flutter riverpod - 2

Pyo·2023년 10월 2일
0

이어서 정리해보겠다.

Modifier

FamilyModifier

Riverpod에서 family modifier는 특정 인수 또는 키에 따라 동적으로 상태를 생성하거나 제공하는 데 사용되는 중요한 개념 중 하나이다. 다시 말해서 family modifier를 사용하면 인스턴스 생성시 매개변수를 받아서 매개변수에 따라 다른 데이터를 받을수 있다. 코드와 결과로 보자.

family_modifier_provider.dart

import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_riverpod_playground/screen/family_modifier_screen.dart';

final familyModifierProvider =
    FutureProvider.family<List<int>, int>((ref, data) async {
  await Future.delayed(
    Duration(seconds: 2),
  );

  return List.generate(5, (index) => index * data);
});

family_modifier_screen.dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_riverpod_playground/riverpod/family_modifier_provider.dart';

class FamilyModifierScreen extends ConsumerWidget {
  const FamilyModifierScreen({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final state1 = ref.watch(familyModifierProvider(3));
    final state2 = ref.watch(familyModifierProvider(5));

    return Scaffold(
      appBar: AppBar(
        title: const Text('FamilyModfierScreen'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            state1.when(
                data: (data) => Text(data.toString()),
                error: (err, stack) => Text(err.toString()),
                loading: () => const CircularProgressIndicator()),
            state2.when(
                data: (data) => Text(data.toString()),
                error: (err, stack) => Text(err.toString()),
                loading: () => const CircularProgressIndicator()),
          ],
        ),
      ),
    );
  }
}

기존 방식과는 다르게 Provider 생성시 family를 이용하여 생성하고,매개변수를 받을수 있다. 따라서 Provider를 watch할때 매개변수를 넣어주게 되면 다른 결과값을 얻을 수 있다.

AutoDisposeModifier

일반적으로 Flutter 앱에서는 위젯이 화면에서 사라질 때 관련된 데이터나 리소스를 해제해야 한다.
Dispose는 메모리 누수를 방지하고 필요 없는 리소스를 해제하는 데 중요한 역할을 한다. AutoDisposeModifier는 modifier는 상태 관리 중에 자동으로 프로바이더를 "dispose"하는데 사용된다. 이것은 특히 페이지나 화면이 이동될 때, 위젯이 제거될 때 또는 화면이 더 이상 해당 Provider에 의존하지 않을 때 유용하다. 사용방법을 알아보자.

auto_dispose_modifier_provider.dart

import 'package:flutter_riverpod/flutter_riverpod.dart';

final autoDisposeModifierProvider =
    FutureProvider.autoDispose<List<int>>((ref) async {
  await Future.delayed(Duration(seconds: 2));

  return [1, 2, 3, 4, 5];
});

auto_dispose_modifier_screen.dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_riverpod_playground/riverpod/auto_dispose_modifier_provider.dart';

class AutoDisposeModifierScreen extends ConsumerWidget {
  const AutoDisposeModifierScreen({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final state = ref.watch(autoDisposeModifierProvider);
    return Scaffold(
        appBar: AppBar(
          title: Text('AutoDisposeModifierScreen'),
        ),
        body: Center(
          child: state.when(
              data: (data) => Text(data.toString()),
              error: (err, stack) => Text(err.toString()),
              loading: () => const CircularProgressIndicator()),
        ));
  }
}


AutoDisposeModifier는 Provider 생성시 autoDispose를 이용하여 생성을 해서 평소처럼 사용을 하게되면 자동으로 autoDispose를 해준다.

Provider 안에서 Provider 사용

하나의 provider안에서 여러개의 provider를 사용해 보는 코드를 작성하겠다. 운동의 종류를 구분하는 기능을 구현해 볼 것이다.
우선 기능구현을 위해 기존 사용한 WorkoutModel을 수정한다.

workout_model.dart

class WorkoutModel {
  final String name;
  final int weight;
  final bool isSuccess;
  final String target;

  WorkoutModel({
    required this.name,
    required this.weight,
    required this.isSuccess,
    required this.target,
  });

  WorkoutModel copyWith({
    String? name,
    int? weight,
    bool? isSuccess,
    String? target,
  }) {
    return WorkoutModel(
        name: name ?? this.name,
        weight: weight ?? this.weight,
        isSuccess: isSuccess ?? this.isSuccess,
        target: target ?? this.target);
  }
}

workout_provider.dart

import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_riverpod_playground/model/workout_model.dart';
import 'package:flutter_riverpod_playground/riverpod/state_notifier_provider_2.dart';

final filteredWorkoutProvider = Provider<List<WorkoutModel>>((ref) {
  final workoutListState = ref.watch(workoutListProvider);
  final filterState = ref.watch(filterProvider);

  if (filterState == FilterState.all) {
    return workoutListState;
  }
  return workoutListState
      .where((e) => filterState == FilterState.upper
          ? e.target == '상체'
          : e.target == '하체')
      .toList();
});

final filterProvider = StateProvider<FilterState>((ref) => FilterState.all);

enum FilterState { upper, lower, all }

final workoutListProvider =
    StateNotifierProvider<WorkOutListNotfier, List<WorkoutModel>>(
        (ref) => WorkOutListNotfier());

class WorkOutListNotfier extends StateNotifier<List<WorkoutModel>> {
  WorkOutListNotfier()
      : super(
          [
            WorkoutModel(
                name: '벤치프레스', weight: 105, isSuccess: true, target: '상체'),
            WorkoutModel(
                name: '데드리프트', weight: 200, isSuccess: true, target: '하체'),
            WorkoutModel(
                name: '스쿼트', weight: 170, isSuccess: false, target: '하체'),
            WorkoutModel(
                name: '바벨로우', weight: 100, isSuccess: true, target: '상체'),
            WorkoutModel(
                name: '인클라인벤치프레스', weight: 90, isSuccess: false, target: '상체'),
            WorkoutModel(
                name: 'OHP', weight: 65, isSuccess: false, target: '상체'),
            WorkoutModel(
                name: '런지', weight: 700, isSuccess: false, target: '하체'),
          ],
        );

}

workout_screen.dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_riverpod_playground/model/workout_model.dart';
import 'package:flutter_riverpod_playground/riverpod/workout_provider.dart';

class WorkoutScreen extends ConsumerWidget {
  const WorkoutScreen({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final state = ref.watch(filteredWorkoutProvider);
    return Scaffold(
      appBar: AppBar(
        title: Text('WorkoutScreen'),
        actions: [
          PopupMenuButton(
            itemBuilder: (_) => FilterState.values
                .map((e) => PopupMenuItem(
                      value: e,
                      child: Text(e.name),
                    ))
                .toList(),
            onSelected: (value) {
              ref.read(filterProvider.notifier).update((state) => value);
            },
          ),
        ],
      ),
      body: ListView(
          children: state
              .map((e) => Center(
                      child: Text(
                    e.name,
                    style: const TextStyle(
                        fontSize: 20, fontWeight: FontWeight.bold),
                  )))
              .toList()),
    );
  }
}

filteredWorkoutProvider에서는 filterProvider의 값을 바꿔주면 workoutListProvider를 filterProvider의 조건에 맞게 반환하도록 하나의 provider안에서 provider 두개를 조합하여 사용하고 있다. 적절하게 Provider를 중첩하여 사용한다면 코드를 모듈화하고 재사용성을 높이는 등 여러가지 장점을 가질 수 있다.


Provider Observer

Riverpod 라이브러리에서 제공하는 기능 중 하나로, 상태 변화를 감지하고 이에 대한 처리를 수행하기 위한 도구이다. ProviderObserver를 사용하면 Provider의 상태가 변경될 때 특정 작업을 수행하거나, UI를 업데이트하는 등의 작업을 수행할 수 있다.

provider_observer.dart

import 'package:flutter_riverpod/flutter_riverpod.dart';

class Logger extends ProviderObserver {
  @override
  void didUpdateProvider(ProviderBase<Object?> provider, Object? previousValue,
      Object? newValue, ProviderContainer container) {
    print(
        '[Provider Updated] provider : $provider / previousValue : $previousValue , newValue : $newValue ');
  }

  @override
  void didAddProvider(ProviderBase<Object?> provider, Object? value,
      ProviderContainer container) {
    print('[Provider Added] provider : $provider / value : $value');
  }

  @override
  void didDisposeProvider(
      ProviderBase<Object?> provider, ProviderContainer container) {
    print('[Provider Disposed] provider : $provider');
  }
}

main.dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_riverpod_playground/riverpod/provider_observer.dart';
import 'package:flutter_riverpod_playground/screen/home_screen.dart';

void main() {
  runApp(
    ProviderScope(
      observers: [Logger()],
      child: MaterialApp(
        home: HomeScreen(),
      ),
    ),
  );
}

사용목적은 주로 로그 확인을 위해 사용을 하고 사용방법은 간단하다. ProviderObserver 상속받은 클래스를 ProviderScope에 observers에 사용하면된다. Provider 추가할때,상태 업데이트할때,Provider 해제할때의 로그를 확인할 수 있다.

참고

https://www.inflearn.com/course/%ED%94%8C%EB%9F%AC%ED%84%B0-%EC%8B%A4%EC%A0%84/dashboard

0개의 댓글