[flutter] 플러터 Riverpod와 페이징 처리 구현 방법

이번 글에서는 플러터(Flutter) 앱 개발을 위한 상태 관리 라이브러리인 Riverpod와 페이징 처리(Pagination)를 구현하는 방법에 대해 알아보겠습니다.

1. Riverpod란?

Riverpod는 플러터의 상태 관리를 위한 라이브러리입니다. 기존의 Provider 패키지보다 향상된 기능과 사용이 편리한 문법을 제공하여 앱의 상태와 종속성을 관리할 수 있습니다. Riverpod는 의존성 주입(Dependency Injection)과 의존성 관리(Dependency Management)를 쉽게 해줍니다.

2. 페이징 처리 구현 방법

페이징 처리는 대부분 웹 및 모바일 앱에서 많은 양의 데이터를 조금씩 로드하는 데 사용됩니다. 즉, 한 번에 모든 데이터를 로드하지 않고 필요한 만큼을 가져와서 메모리 절약과 성능 향상을 도모할 수 있습니다.

2.1. 필요한 패키지 추가

페이징 처리를 구현하기 위해 flutter_bloc, equatable, 그리고 http 패키지를 추가해야 합니다. 이 패키지들은 페이징 처리와 네트워크 요청을 위해 필요한 도구들을 제공합니다.

dependencies:
  flutter_bloc: ^7.0.0
  equatable: ^2.0.0
  http: ^0.13.4

2.2. 데이터 모델 작성

먼저, 페이징 처리할 데이터의 모델을 작성합니다. 예를 들어, 사용자 정보를 나타내는 User 클래스를 생성합니다.

class User {
  final int id;
  final String name;
  final String email;

  User({required this.id, required this.name, required this.email});

  factory User.fromJson(Map<String, dynamic> json) {
    return User(
      id: json['id'],
      name: json['name'],
      email: json['email'],
    );
  }
}

2.3. 리포지토리 작성

데이터를 가져오는 리포지토리 클래스를 작성합니다. 이 클래스에서는 네트워크 요청을 통해 페이지별 데이터를 가져오고, 해당 데이터를 파싱하여 사용자 리스트를 반환합니다.

import 'package:http/http.dart' as http;

class UserRepository {
  Future<List<User>> getUsers(int page) async {
    final response = await http.get(Uri.parse('https://example.com/api/users?page=$page'));
    
    if (response.statusCode == 200) {
      final data = jsonDecode(response.body) as List<dynamic>;
      final users = data.map((json) => User.fromJson(json)).toList();
      return users;
    } else {
      throw Exception('Failed to load users');
    }
  }
}

2.4. 상태 관리와 페이징 처리

Riverpod를 사용하여 상태 관리와 페이징 처리를 구현합니다. StateNotifier, StateNotifierProvider, 그리고 SafeAutoDisposeStateNotifierProvider을 사용하여 상태를 관리하고, StreamControllerStreamProvider를 사용하여 페이징 처리를 구현할 수 있습니다.

class UserProvider extends StateNotifier<List<User>> {
  UserProvider() : super([]);

  UserRepository _userRepository = UserRepository();
  int _currentPage = 1;

  Future<void> getUsers() async {
    try {
      final users = await _userRepository.getUsers(_currentPage);
      state = [...state, ...users];
      _currentPage++;
    } catch (e) {
      // 오류 처리
    }
  }
}

final userProvider = StateNotifierProvider<UserProvider>((ref) => UserProvider());
final userStreamProvider = StreamProvider.autoDispose<List<User>>((ref) => Stream.fromFuture(ref.watch(userProvider.notifier).getUsers()));

2.5. UI 작성

마지막으로, UI를 작성하여 사용자 정보를 출력하고 페이징 처리를 구현합니다.

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final userStream = context.read(userStreamProvider);

    return Scaffold(
      appBar: AppBar(
        title: Text('User List'),
      ),
      body: userStream.when(
        data: (users) {
          return ListView.builder(
            itemCount: users.length,
            itemBuilder: (context, index) => ListTile(
              title: Text(users[index].name),
              subtitle: Text(users[index].email),
            ),
          );
        },
        loading: () => Center(child: CircularProgressIndicator()),
        error: (error, stackTrace) => Center(child: Text('Error occurred: $error')),
      ),
    );
  }
}

이제 페이징 처리가 적용된 사용자 정보를 볼 수 있는 화면이 나타납니다. 스크롤을 내리면 추가 데이터가 로드되어 화면에 표시됩니다.

결론

이제 Riverpod와 페이징 처리를 사용하여 플러터 앱에서 많은 양의 데이터를 효과적으로 관리할 수 있습니다. 상태 관리와 네트워크 요청 로직을 분리하여 코드를 더욱 모듈화하고 유지 관리하기 쉽게 만들었습니다.