Riverpod을 활용한 UI 상태관리

2024-02-25 19:56 | Flutter

Flutter는 선언형 UI 프로그래밍 방식이기 때문에 상태 관리가 매우 중요하며, Riverpod을 이용하면 상태 관리를 효율적으로 할 수 있고 데이터 바인딩을 통해 UI를 자동으로 업데이트 할 수 있다.

Riverpod이란?

Riverpod은 InheritedWidget과 비슷한 동작을 하는 새로운 매커니즘으로 재구현한 상태 관리 패키지이다. Riverpod은 리액티브 캐싱, 데이터 바인딩 프레임워크이며 리액티브 캐싱과 데이터 바인딩 기능의 정의는 아래와 같다.

  • 리액티브 캐싱(Reactive Caching) : 데이터를 비동기적으로 계산 할 때 캐싱하여 해당 데이터가 필요한 모든 곳에서 쉽게 접근할 수 있도록 하는 기술이다. Riverpod에서는 Provider를 이용하여 상태를 지속적으로 캐시하고 노출함으로써 데이터를 쉽게 공유하고 동기화할 수 있다. 예를 들어, 처음 데이터를 가져온 뒤에는 동일한 데이터를 다른 컴포넌트에서 다시 가져올 필요 없이, 이미 캐시된 데이터를 활용하여 중복 호출을 줄이며 상태를 효율적으로 관리하고 성능을 향상시킬 수 있다.

  • 데이터 바인딩(Data Binding) : UI와 데이터를 결합하는 기술로 데이터의 변경에 따라 UI를 자동으로 변경하도록 해준다. Riverpod을 이용하면 상태를 나타내는 Provider 및 ref 객체의 watch 메소드를 통해서 상태의 변경을 관찰하여 UI가 자동으로 업데이트되도록 할 수 있다. 즉, MVVM 패턴을 구현할 수 있다.

ProviderScope

ProviderScope는 정의한 Provider의 상태를 저장하는 위젯이다. Provider가 정의될 때 ProviderScope 내부적으로 ProviderContainer 인스턴스를 생성한다.

void main() {
  WidgetsFlutterBinding.ensureInitialized();
 
  runApp(
    const ProviderScope(
      child: FlutterRiverpodTodoApp(),
    ),
  );
}
 

ref 객체 얻기

프로바이더로 부터 "ref" 객체 얻기

프로바이더를 읽기전에 "ref" 객체를 얻어야 한다. "ref"는 프로바이더간에 상호작용을 도와주고 위젯이나 다른 프로바이더에서 얻을 수 있다.

위젯에서 "ref" 객체 얻기

위젯들은 "ref" 파라미터를 가지고 있지 않으므로 Riverpod에서는 위젯에서 "ref" 객체를 얻기위한 솔루션을 제공합니다. 기본적으로 위젯을 생성할 때 상속받는 StatelessWidget 대신 ConsumerWidget으로 상속받기 가장 기본적으로 위젯에서 "ref" 객체를 얻기위한 방법입니다. ConsumerWidget은 기본적으로 StatelessWidget 과 동일합니다. 차이가 있다면 build메소드에 추가적으로 "ref" 객체를 받습니다.

-StatelessWidget

  class MyApp extends StatelessWidget {
  const MyApp({super.key});
  
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text("Riverpod Study")),
        body: Center(child: Text("Hello world")),
      ),
    );
  }
}

-ConsumerWidget

class MyApp extends ConsumerWidget {
  const MyApp({super.key});
 
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    
    final String value = ref.watch(helloWorldProvider);
 
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text("Riverpod Study")),
        body: Center(child: Text(value)),
      ),
    );
  }
}
 

StatefulWidget을 ConsumerStatefulWidget으로 변경

-StatefulWidget

class HomeViewState extends StatefulWidget {
  const HomeViewState({Key? key}) : super(key: key);
 
  @override
  State<HomeViewState> createState() => _HomeViewStateState();
}
 
class _HomeViewStateState extends State<HomeViewState> {
  @override
  Widget build(BuildContext context) {
    return Container();
  }
}

-ConsumerStatefulWidget 변경

"ref"는 StatefulWidget의 모든 생명주기 상에서 사용할 수 있습니다.
또한 "ref"는 build 메소드 안에서 프로바이더를 구독(listen)하기위해 사용할 수 있습니다.

class HomeView extends ConsumerStatefulWidget {
  const HomeView({Key? key}) : super(key: key);
 
  @override
  HomeViewState createState() => HomeViewState();
}
 
class HomeViewState extends ConsumerState<HomeView> {
  @override
  void initState() {
    super.initState();
    
    ref.read(counterProvider);
  }
 
  @override
  Widget build(BuildContext context) {
    
    final counter = ref.watch(counterProvider);
    return Text('$counter');
  }
}

ConsumerWidget 과의 차이점은 build 메소드에 ref객체를 전달하지 않습니다.
ConsumerState 객체에 "ref"객체가 포함됩니다.