본문 바로가기
Flutter

Flutter에서 스크롤 시 ListView 아이템 재로딩 문제 해결 방법

by Maccrey Coding 2024. 7. 27.
반응형

 

ListView에서 스크롤 시 아이템이 재로딩되는 현상은 일반적으로 상태를 적절하게 관리하지 않거나, 특히 이미지 로드와 관련해서는 캐싱이 제대로 되지 않을 때 발생합니다. 이를 해결하기 위해서는 다음과 같은 방법을 사용할 수 있습니다.

1. `ListView.builder` 사용


`ListView.builder`는 아이템을 효율적으로 렌더링하기 때문에, 스크롤 시 전체 아이템을 다시 빌드하는 것이 아니라 필요한 아이템만 빌드합니다. 

이렇게 하면 불필요한 렌더링을 줄일 수 있습니다.

ListView.builder(
  itemCount: items.length,
  itemBuilder: (context, index) {
    return ListTile(
      title: Text(items[index].value),
    );
  },
)



2. `CachedNetworkImage` 사용


이미지를 네트워크에서 가져올 때는 `cached_network_image` 패키지를 사용하면 이미지가 캐싱되어 스크롤 시 재로딩되는 현상을 방지할 수 있습니다.

`pubspec.yaml`에 패키지를 추가합니다.

dependencies:
  cached_network_image: ^3.2.1



그런 다음 이미지를 로드할 때 `CachedNetworkImage`를 사용합니다.

import 'package:cached_network_image/cached_network_image.dart';

// ...

CachedNetworkImage(
  imageUrl: items[index].imageUrl,
  placeholder: (context, url) => CircularProgressIndicator(),
  errorWidget: (context, url, error) => Icon(Icons.error),
)



3. `AutomaticKeepAliveClientMixin` 사용


아이템이 복잡하거나 상태를 유지해야 하는 경우 `AutomaticKeepAliveClientMixin`을 사용하여 아이템의 상태를 유지할 수 있습니다.

class MyListItem extends StatefulWidget {
  final Item item;

  MyListItem({required this.item});

  @override
  _MyListItemState createState() => _MyListItemState();
}

class _MyListItemState extends State<MyListItem> with AutomaticKeepAliveClientMixin {
  @override
  bool get wantKeepAlive => true;

  @override
  Widget build(BuildContext context) {
    super.build(context); // Don't forget to call super.build().

    return ListTile(
      title: Text(widget.item.value),
      leading: CachedNetworkImage(
        imageUrl: widget.item.imageUrl,
        placeholder: (context, url) => CircularProgressIndicator(),
        errorWidget: (context, url, error) => Icon(Icons.error),
      ),
    );
  }
}



그리고 `ListView.builder`에서 `MyListItem`을 사용합니다.

ListView.builder(
  itemCount: items.length,
  itemBuilder: (context, index) {
    return MyListItem(item: items[index]);
  },
)



4. 데이터 모델에 `Equatable` 사용


`Equatable` 패키지를 사용하면 아이템의 상태를 비교하여 변경된 경우에만 다시 빌드할 수 있습니다. 

이는 특히 아이템의 상태 관리와 관련하여 유용합니다.

dependencies:
  equatable: ^2.0.3



아이템 모델을 `Equatable`을 사용하여 정의합니다.

import 'package:equatable/equatable.dart';

class Item extends Equatable {
  final String id;
  final String value;
  final String imageUrl;

  Item({required this.id, required this.value, required this.imageUrl});

  @override
  List<Object> get props => [id, value, imageUrl];
}



이렇게 하면 `ListView`의 아이템 상태 관리가 더욱 효율적으로 이루어집니다.

 

5. cacheExtent 사용

 

cacheExtent 속성을 사용하여 미리 로드할 아이템의 범위를 설정합니다.

다음은 전체적인 코드 예제입니다.

import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_database/firebase_database.dart';
import 'package:hive/hive.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'item_model.dart';
import 'firebase_options.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );
  await Hive.initFlutter();
  Hive.registerAdapter(ItemAdapter());
  await Hive.openBox<Item>('itemsBox');
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final DatabaseReference databaseReference = FirebaseDatabase.instance.reference();
  Box<Item>? itemsBox;

  @override
  void initState() {
    super.initState();
    itemsBox = Hive.box('itemsBox');
    syncData();
  }

  Future<void> syncData() async {
    databaseReference.child('items').onValue.listen((event) async {
      var snapshot = event.snapshot;
      Map<dynamic, dynamic>? items = snapshot.value as Map<dynamic, dynamic>?;
      if (items != null) {
        await itemsBox!.clear();
        items.forEach((key, value) async {
          await itemsBox!.put(key, Item(id: key, value: value['value'], imageUrl: value['imageUrl']));
        });
      }
      setState(() {});
    });
  }

  @override
  Widget build(BuildContext context) {
    List<Item> items = itemsBox!.values.toList().cast<Item>();
    return Scaffold(
      appBar: AppBar(
        title: Text('Flutter Demo Home Page'),
      ),
      body: ListView.builder(
        itemCount: items.length,
        itemBuilder: (context, index) {
          return MyListItem(item: items[index]);
        },
        cacheExtent: 1000, // Increase this value to cache more items
      ),
    );
  }
}

class MyListItem extends StatefulWidget {
  final Item item;
  MyListItem({required this.item});

  @override
  _MyListItemState createState() => _MyListItemState();
}

class _MyListItemState extends State<MyListItem> with AutomaticKeepAliveClientMixin {
  @override
  bool get wantKeepAlive => true;

  @override
  Widget build(BuildContext context) {
    super.build(context); // Don't forget to call super.build().
    return ListTile(
      title: Text(widget.item.value),
      leading: CachedNetworkImage(
        imageUrl: widget.item.imageUrl,
        placeholder: (context, url) => CircularProgressIndicator(),
        errorWidget: (context, url, error) => Icon(Icons.error),
      ),
    );
  }
}

설명

  1. ListView.builder: 효율적으로 아이템을 렌더링합니다.
  2. CachedNetworkImage: 이미지를 캐싱하여 네트워크 요청을 줄입니다.
  3. AutomaticKeepAliveClientMixin: 각 아이템의 상태를 유지합니다.
  4. Equatable : 특히 아이템의 상태 관리와 관련하여 유용합니다.
  5. cacheExtent: ListView.builder의 cacheExtent 속성을 사용하여 화면 외부에 있는 아이템을 미리 로드합니다. 예제에서는 1000 픽셀의 범위를 미리 로드하도록 설정했습니다.

이렇게 하면 ListView의 성능이 개선되고, 스크롤 시 아이템이 재로딩되는 현상을 방지할 수 있습니다.

필요에 따라 cacheExtent 값을 조정하여 성능과 메모리 사용량 간의 균형을 맞출 수 있습니다.

 

이 방법들을 결합하여 적용하면 ListView에서 스크롤 시 아이템이 재로딩되는 문제를 해결할 수 있습니다.

 

Starting Google Play App Distribution! "Tester Share" for Recruiting 20 Testers for a Closed Test.

 

Tester Share [테스터쉐어] - Google Play 앱

Tester Share로 Google Play 앱 등록을 단순화하세요.

play.google.com

 

반응형