Provider
Provider 패키지는 Flutter의 상태 관리를 단순화하고 향상시키는 데 사용된다. Provider를 사용하면 데이터와 위젯을 분리하고, 상태의 변경을 효율적으로 관리할 수 있으며, 앱 전체에서 쉽게 해당 모델에 접근할 수 있게 한다.
Provider 종류:
Provider
사용의 주요 이점:
상태 분리: 데이터 모델과 UI 로직을 분리하여, 각각의 코드를 더 쉽게 관리하고 유지할 수 있다.
효율적인 리소스 관리: 필요한 위젯만 리스너를 통해 재빌드되므로, 불필요한 리소스 낭비를 줄일 수 있다.
코드 재사용성: Provider를 통해 생성한 데이터 객체는 앱 전체에서 재사용할 수 있다.
Provider상태관리
Provider을 사용하기위해 main.dart 최상위 위젯을 ChangeNotifierProvider
로 감싸준다. (Provider가 여러개 사용될 경우, MultiProvider
를 사용)
create?
create
콜백은 Provider
패키지에서 ChangeNotifierProvider
와 같은 provider 위젯을 사용할 때 필수적이다. 이 콜백은 provider에 의해 관리되는 데이터 모델의 인스턴스를 생성하는 역할을 하고, 여기에는 몇 가지 중요한 이유가 있다:
초기화:
create
는 데이터 모델이나ChangeNotifier
객체를 초기화하는 데 사용된다. 즉, 위젯 트리가 빌드될 때 한 번 호출되어 객체를 생성하고, 이 객체는Provider
에 의해 트리 전체에 제공된다.소유권과 생명주기 관리:
create
에 의해 생성된 객체는Provider
에 의해 소유되며,Provider
가 범위에서 벗어날 때 적절히 제거된다. 이렇게 함으로써 메모리 누수를 방지하고 객체의 생명주기를 효과적으로 관리할 수 있다.성능 최적화:
create
는 객체를 필요할 때만 생성한다. 즉, 위젯이 화면에 표시되지 않는 경우 객체가 불필요하게 메모리를 차지하지 않도록 합니다.컨텍스트 접근성:
create
함수는BuildContext
를 매개변수로 받기 때문에, 생성되는 객체가 컨텍스트를 기반으로 추가적인 초기화 작업을 수행할 수 있다. 예를 들어, 다른Provider
에서 제공하는 데이터에 의존하는 객체를 생성할 때 유용하다.유연성:
Provider
를 사용하면 단일 객체 뿐만 아니라 객체의 리스트나 맵 등 다양한 종류의 데이터를 관리할 수 있으며,create
를 통해 이러한 데이터 구조를 초기화할 수 있다.
“그렇다면, 그 값은 어떻게 들을 수 있을까? 다른 화면에서 ChangeNotifier에 어떻게 접근 할 수 있을까?”
위 예시 코드 context.watch<VideoConfig>().isMuted
처럼 ‘< >’부분에 접근하고자 하는 ChangeNotifierProvider 타입을 적어주면, 해당 데이터에 접근 할 수 있다.
ChangeNotifierProvider의 타입을 참조하는 이유?
BuildContext를 통해 특정 타입의 ChangeNotifier를 올바르게 찾아 리스닝하도록 하기 위함이다. 제네릭 타입 T를 사용하여 Provider의 타입을 지정함으로써, Provider 트리에서 해당 타입의 가장 가까운 인스턴스를 찾을 수 있다.
1
2
3
4
5
6
7
8
9
*제네릭 타입?
제네릭 타입 T는 프로그래밍에서 사용되는 일종의 플레이스홀더 또는 매개변수화된 타입을 의미한다. 제네릭을 사용하면 같은 코드를 다양한 데이터 타입에 대해 재사용할 수 있다. 즉, 타입을 직접 명시하는 대신에, 타입 매개변수 T를 사용하여 코드를 보다 일반적이고 유연하게 작성할 수 있다.
예를 들어, Dart에서 List<T>는 T라는 타입 매개변수를 사용하는 제네릭 리스트이다. 여기서 T는 리스트에 들어갈 요소의 타입을 나타낸다. List<int>는 정수 타입의 요소만을 갖는 리스트를, List<String>은 문자열 타입의 요소만을 갖는 리스트를 의미한다.
Flutter에서 Provider를 사용할 때 ChangeNotifierProvider<T> 같은 형태로 T를 사용하는 것도 같은 원리이다. 여기서 T는 ChangeNotifierProvider가 제공할 데이터 모델의 타입을 지정한다. 이렇게 함으로써 Provider 트리에서 해당 타입의 객체를 찾을 수 있고, context.watch<T>() 또는 context.read<T>()와 같은 메서드를 사용하여 타입 안전성을 보장하며 해당 데이터에 접근할 수 있다.
이렇게 제네릭 타입을 사용함으로써 타입 안전성을 확보하고, 코드의 재사용성을 높이며, 개발자의 의도를 명확하게 전달할 수 있다.
ChangeNotifierProvider의 타입을 참조하지 않으면?
타입 T를 지정하지 않으면, Provider는 기본적으로 dynamic 타입으로 처리되고, 컴파일 타임에 타입 체크를 수행할 수 없다. 이는 다음과 같은 문제를 일으킬 수 있다:
타입 안정성 부족: 특정 타입을 기대하는데, 다른 타입이 반환되면 런타임 오류가 발생한다.
디버깅 어려움: 오류가 발생했을 때 문제의 원인을 찾기 어렵다.
코드 가독성 저하: 타입이 명시되지 않으면 코드를 읽는 다른 개발자에게 혼란을 줄 수 있다.
context.watch() / context.read()
Flutter의 provider
패키지에서 context.watch<T>()
와 context.read<T>()
는 Provider
로부터 데이터를 가져오는 두 가지 다른 방법이다. 각각의 메서드는 상이한 사용 시나리오와 목적을 가지고 있다.
context.watch()
목적:
watch
는 위젯을 해당 타입T
의 데이터에 대한 리스너로 등록한다. 즉, 데이터T
가 변경될 때마다 위젯이 재빌드된다.사용 시나리오:
watch
는 주로build
메서드 내부에서 사용되며, 해당 데이터의 변경을 UI에 반영해야 할 때 사용된다.동작 방식: 위젯이
Provider
의 데이터를 “구독”하게 되고, 데이터가 변경될 때마다Provider
는notifyListeners()
호출을 통해watch
를 사용하는 위젯에게 데이터 변경을 알린다. 그러면watch
를 사용하는 위젯은 자동으로 재빌드된다.
context.read()
목적:
read
는 단순히 데이터를 한 번 읽어오는 데 사용된다.watch
와 달리 위젯을 데이터의 리스너로 만들지 않는다.build
메서드 외부에서 사용될 때가 많으며, 주로 이벤트 핸들러나 생명주기 메서드 내에서 데이터에 접근할 때 사용된다.사용 시나리오:
read
는 위젯이 데이터를 단순히 읽기만 하고, 그 데이터의 변경에 의해 위젯이 재빌드될 필요가 없을 때 사용된다.동작 방식:
read
를 호출할 때,Provider
는 데이터 인스턴스를 반환하지만, 이를 호출한 위젯을 데이터 변경에 대한 리스너로 등록하지는 않는다. 따라서 데이터가 변경되어도read
를 호출한 위젯은 재빌드되지 않는다.
“
watch
는 데이터의 변화를 감시하고 변화가 있을 때마다 위젯을 재빌드하기 위해 사용되며,read
는 데이터를 한 번만 읽어올 때 사용되어 위젯의 재빌드 없이 데이터에 접근하고자 할 때 적합하다.”
setting 화면 설정에 따른 VideoScreen 상태변화