provider를 이용해서 상태관리를 하면 앱 내부에서 트리구조 상 멀리 떨어진 변수들도 쉽게 한곳에서 관리할 수 있습니다. 이전 글에서는 provider를 이용하기 위한 가장 기본적인 방법에 대해서 알아보았고 오늘 글에서는 provider 변수 이용방법 3가지에 대해서 알아보도록 하겠습니다.
들어가기에 앞서
본 글은 provider를 이용한 상태관리에 대한 연재글이며 다음의 글들에 이어지는 내용입니다.
ChangeNotifier, addListener를 이용해 앱 상태관리하기
Flutter Provider 패키지의 가장 기본 사용방법
provider 변수 이용 방법 1) Provider.of<T>(context)
provider의 변수를 사용하는 가장 기본적인 방법은 Provider.of<T>를 이용하는 것입니다. 앞선 예제에서 counter를 구현하면서 과를 저장하는 count 변수 및 값을 증가시키는 add() 함수와 값을 감소시키는 remove() 함수를 ChangeNotifier를 상속받은 CounterProvider 클래스로 정의하였습니다. 지난 글에서 provider를 이용한 예제를 다시 한번 살펴보겠습니다.
import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../provider/count_provider.dart'; class Counter extends StatelessWidget { Counter({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text("Provider Sample"), ), body: Center( child: Text(Provider.of<CountProvider>(context).count.toString(), style: const TextStyle(fontSize: 24)), ), floatingActionButton: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ IconButton( icon: const Icon(Icons.add), onPressed: () { Provider.of<CountProvider>(context).add(); }, ), IconButton( icon: const Icon(Icons.remove), onPressed: () { Provider.of<CountProvider>(context).remove(); }, ), ], ), ); } }
CounterProvider 클래스를 안에 정의된 변수 및 함수를 이용하기 위해서는 UI 중간에서 Provider.of<CounterProvider>(context) 를 이용하면 됩니다. 변수인 count에 접근하려면 Provider.of<CounterProvider>(context).count로 add()나 remove()함수에 접근하려면 Provider.of<CounterProvider>(context).add(), Provider.of<CounterProvider>.remove()와 같이 접근하면 됩니다. Provider.of<CounterProvider>(context)를 인스턴스로 정의하여 사용할수도 있습니다.
listen : false / true 설정
Provider.of<T>(context)를 이용하는 이유 중 하나는 변수의 변화를 UI에 반영하기 위함입니다. CounterProvider 클래스에서 정의한 count 변수의 변화를 일으키는 함수인 add()와 remove() 함수에는 notifyListeners() 함수가 포함되어 있습니다. 이 함수는 각각 count 변수를 변화시킨 뒤 count 변수를 이용하고 있는 위젯들에게 변경상황을 전파하는 역할을 하게 됩니다.
count 변수를 이용하고 있는 Provider.of<T>(context) 위젯은 변경상황을 notifyListeners()로부터 듣고 변화된 값을 UI에 반영하게 됩니다. 하지만 모든 위젯이 업데이트가 필요한 것은 아닙니다. Provider.of<CounterProvider>(context).add(), Provider.of<CounterProvider>.remove()와 같은 위젯들은 변화를 일으키는 이벤트를 발생시키는 역할만을 할 뿐 count 변수의 변화로 인해서 UI의 변화가 필요하지는 않습니다. 기본적으로 모두 notifyListensers() 함수가 호출되면 UI를 업데이트하지만 리소스 절약을 위해서 업데이트를 수행하지 않도록 할 수 있는데 이 옵션이 listen : false 입니다.
add()와 remove() 함수를 적용하는 provider에 다음과 같이 listen : false 옵션을 적용하면 더 이상 상태변화에 대해서 UI가 업데이트되지 않습니다.
Provider.of<CounterProvider>(context, listen : false).add()
Provider.of<CounterProvider>(context, listen : false).remove()
import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../provider/count_provider.dart'; class Counter extends StatelessWidget { Counter({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text("Provider Sample"), ), body: Center( child: Text(Provider.of<CountProvider>(context).count.toString(), style: const TextStyle(fontSize: 24)), ), floatingActionButton: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ IconButton( icon: const Icon(Icons.add), onPressed: () { Provider.of<CountProvider>(context, listen : false).add(); }, ), IconButton( icon: const Icon(Icons.remove), onPressed: () { Provider.of<CountProvider>(context, listen : false).remove(); }, ), ], ), ); } }
별도의 listen 옵션이 설정되어 있지 않으면 true로 기본값이 설정되어 있는 것이며, 이는 상태변화에 대해 UI가 업데이트 됩니다.
provider 변수 이용 방법 2) context.watch()와 context.read() 이용하기
Provider.of<T>(context)를 동일하게 대체할 수 있는 방법이 있는데 그것이 바로 context.watch()와 context.read()를 이용하는 것입니다. 상태변화를 인지하였을 때 watch는 UI의 업데이트를 하고 read는 UI의 업데이트를 수행하지 않습니다. context.watch()는 Provider.of<T>(context, listen : true)와 동일하고 context.read()는 Provider.of<T>(context, listen : false)와 각각 매칭된다고 보시면 됩니다. 기존의 Provider.of<T>(context) 대신에 context.watch()나 context.read()를 상황에 맞게 써주면 동일한 결과를 얻을 수 있는 것을 확인하실 수 있습니다.
두가지 방법 간에 차이점이 존재하였으나 이마저도 4.3.3 버전 이후부터는 없어졌다고 합니다. 그래서 두 가지 방법 중 편한 방법을 사용하시면 되겠습니다.
import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../provider/count_provider.dart'; class Counter extends StatelessWidget { Counter({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text("Provider Sample"), ), body: Center( child: Text(context.watch<CountProvider>.count.toString(), style: const TextStyle(fontSize: 24)), ), floatingActionButton: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ IconButton( icon: const Icon(Icons.add), onPressed: () { context.read<CountProvider>.add(); }, ), IconButton( icon: const Icon(Icons.remove), onPressed: () { context.read<CountProvider>.remove(); }, ), ], ), ); } }
provider 변수 이용 방법 3) Consumer 이용하기
마지막으로 사용할 수 있는 방법은 Consumer를 이용하는 것입니다. Consumer를 이용하는 목적은 명확합니다. 하나의 BuildContext 안에서 Provider 생성 및 소비를 모두 수행하고자 할 때 입니다. 보통 provider를 적용하고자 할 때에는 provider를 생성하는 build와 provider를 소비하는 build를 구분하여 사용합니다. 하지만 하나의 BuildContext 안에서 생성 및 사용을 모두 하고자 할 때 기존의 Provider.of<T>(context) 및 context.watch() 방법은 에러를 발생시킵니다. 이 때 사용할 수 있는 것이 바로 consumer입니다.
consumer 위젯은 세 개의 인수를 받습니다. BuildContext와 ChangeNotifier, 그리고 Widget을 인자로 전달 받습니다. consumer 위젯으로 감싸져 있는 내부 위젯들은 ChangeNotifier에 접근하여 직접 변수나 함수를 이용할 수 있게 됩니다. BuildContext나 Widget은 사용하지 않아 인자를 입력하지 않기도 합니다.
앞서 counter 예제에서 consumer를 사용하면 다음과 같이 코드를 변경하면 됩니다.
import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../provider/count_provider.dart'; class Counter extends StatelessWidget { Counter({super.key}); @override Widget build(BuildContext context) { return Consumer<CounterProvider> builder : (_, counter, __) => Scaffold( appBar: AppBar( title: const Text("Provider Sample"), ), body: Center( child: Text(counter.count.toString(), style: const TextStyle(fontSize: 24)), ), floatingActionButton: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ IconButton( icon: const Icon(Icons.add), onPressed: () { counter.add(); }, ), IconButton( icon: const Icon(Icons.remove), onPressed: () { counter.remove(); }, ), ], ), ); } }