-
[Flutter] 플러터 종속성 주입 (InheritedWidgets/ get_it / provider)웹 개발/Flutter 2021. 1. 28. 19:02반응형
개발을 하다보면 들어갈 데이터는 다르지만 UI가 같은 객체를 서로 다른 클래스에 넣어야할 때가 있다.
클래스마다 데이터만 다른 같은 객체를 일일히 추가하다보면 코드가 길어지고
수정사항이 생길 때는 객체별로 코드를 수정해야하기 때문에 매우 번거로워 진다.
이러한 불편함을 해결하기 위해 Dependency Injection (종속성 주입) 기술이 필요하다.
DI는 코드의 재사용성을 높이는 데 사용되는 기술이며 해당 객체를 클래스마다 인스턴스화 즉 생성하는 대신
클래스에 종속 객체를 주입한다.
클래스로부터 객체를 만드는 과정을 클래스의 인스턴스화라고 하며, 어떤 클래스로부터 만들어진 객체를 그 클래스의 인스턴스라고 한다. 결국 인스턴스는 객체와 같은 의미이지만, 객체는 모든 인스턴스를 대표하는 포괄적인 의미를 갖고 있으며, 인스턴스는 어떤 클래스로부터 만들어진 것인지를 강조하는 보다 구체적인 의미를 갖고 있다. <자바의 정석 중>
flutter web의 페이지 네비게이션에서는 DI 개념이 중요하기 때문에 DI 기술들을 알아보았다.
www.filledstacks.com/post/flutter-basics-going-from-set-state-to-architecture/
1) 생성자(constructor)를 이용한 파라미터(parameter) 전달
일반적으로 우리가 가장 먼저 배우는 방법이다.
부모 클래스는 자식 클래스인 HomeView class에 AppInfo 객체의 파라미터(parameter)를 직접 전달하고
전달받은 파라미터는 생성자(constructor)를 통해
HomeView class에 주입되어 HomeView는 AppInfo 객체에 의존하게 된다.
class HomeView extends StatelessWidget { AppInfo appInfo; HomeView({Key key, this.appInfo}) : super(key: key); //생성자 @override Widget build(BuildContext context) { return Container( ); } }
간단하지만 파라미터를 전달해야할 위젯 트리의 깊이가 깊어지거나 전달해야할 파라미터의 수가 많다면
불필요한 코드들이 증가하기 때문에 다른 방법을 찾아야한다.
2) Inherited Widget
state관리를 위해 flutter 패키지에 포함된 위젯이다.
상위 특정 위젯이 하위 위젯과 함께 데이터를 공유하기 위해서 사용되며
상태관리를 하고자하는 하위 위젯들을 Inherited Widget으로 감싼다.
특징
- Inherited Widget으로 감싼 하위위젯들만 상태관리가 가능하기 때문에 일방향적인 데이터 흐름을 강제한다.
- 하위 위젯에서는 데이터 읽기만 가능하고 수정은 상위 위젯에 접근해야만 가능하다.
- context를 사용할 수 없는 위젯에서는 거의 불가능하다.
- of()를 호출할 때마다 해당 메소드가 있는 build context가 재빌드 되기 때문에 성능저하가 발생 할 수 있다.
사용 예시
- 전체 코드
class DIExercisePage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( body: MyStatefulWidget( child: MyContainer( child: DummyContainer( child: Column( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ CounterLabel(), CounterValue(), ], ), ), ), ), ); } }
- 상위 위젯
class MyStatefulWidget extends StatefulWidget { final Widget child; const MyStatefulWidget({Key key, @required this.child}) : super(key: key); static MyStatefulWidgetState of(BuildContext context) { return context.dependOnInheritedWidgetOfExactType<MyInheritedWidget>().data; } @override MyStatefulWidgetState createState() { return MyStatefulWidgetState(); } } class MyStatefulWidgetState extends State<MyStatefulWidget> { int _counterValue = 0; int get counterValue => _counterValue; void addCounterBy1() { setState(() { _counterValue += 1; }); } @override Widget build(BuildContext context) { return MyInheritedWidget( child: widget.child, data: this, ); } }
상위 위젯인 MyStatefulWidget에서는 _counterValue를 parameter로 전달하지 않고
하위 위젯을 MyInheritedWidget로 감싼다.
하위 위젯트리들은 MyInheritedWidget을 통해 상위 위젯의 데이터에 접근할 수 있다.
dependOnInheritedWidgetOfExactType는 하위 위젯 widget.child가 buildcontext에 쌓인
상위 위젯인 MyInheritedWidget 인스턴스에 접근할 수 있도록 한다.- InheritedWidget
class MyInheritedWidget extends InheritedWidget { final MyStatefulWidgetState data; MyInheritedWidget({ Key key, @required Widget child, @required this.data, }) : super(key: key, child: child); @override bool updateShouldNotify(InheritedWidget oldWidget) { return true; } }
updateShouldNotify는 프레임워크가 해당 위젯(oldWidget)이 수정사항이 있을 때
상속받는 위젯들에게 알려 재 빌드되어야 할지 여부를 지정한다.
해당 위젯이 보유하는 데이터가 이전 위젯이 보유하는 데이터와 동일하면
이전 위젯이 보유하는 데이터를 상속받은 위젯을 재구성할 필요가 없다.
- 데이터 수정
class MyContainer extends StatelessWidget { final Widget child; MyContainer({ Key key, @required this.child, }) : super(key: key); void onPressed(BuildContext context) { MyStatefulWidget.of(context).addCounterBy1(); } @override Widget build(BuildContext context) { return Center( child: Container( width: 200, height: 200, child: RaisedButton( color: Colors.red, onPressed: (){ onPressed(context); }, child: child, ), ), ); } }
MyStatefulWidget.of(context).addCounterBy1();
하위 위젯은 static method인 of 를 이용하여 context를 전달하고
상위 위젯인 MyStatefulWidget의 addCounterBy1 함수에 접근하여 데이터를 수정한다.
- 하위 데이터에서 데이터 읽기
class CounterValue extends StatefulWidget { @override State<StatefulWidget> createState() { return CounterValueState(); } } class CounterValueState extends State<CounterValue> { int counterValue; double fontSize; @override void didChangeDependencies() { super.didChangeDependencies(); MyStatefulWidgetState data = MyStatefulWidget.of(context); counterValue = data.counterValue; fontSize = 50.0 + counterValue; } @override Widget build(BuildContext context) { return Text( "$counterValue", style: TextStyle( fontSize: fontSize, color: Colors.white, ), ); } }
MyStatefulWidgetState data = MyStatefulWidget.of(context);
counterValue = data.counterValue;
아래의 코드도 마찬가지로 of를 사용하여 상위 위젯인 MyStatefulWidget의 counterValue에 접근하여
_CounterValue 데이터를 사용했다.
아래의 링크에 가면 더 자세한 원리를 알 수 있다.
medium.com/manabie/how-does-flutter-inheritedwidget-work-3123f9d74c153) get_it
get it package는 간단한 service locator이다.
특징
- global locator를 사용하여 어디에서든 type을 요청가능하다.
- 위젯을 포장하거나 context가 없어도 사용가능하다.
- 인스턴트 추적(instance tracking)은 Factories or Singleton를 통해 자동으로 처리된다.
- flutter의 프로모션과 달리 다방향 데이터 흐름인 전역 객체 사용한다.
사용예시
- 우선 get it package를 pubspec에 추가한다.
dependencies:
get_it: ^5.0.6- service_locator.dart
import 'package:get_it/get_it.dart'; GetIt locator = GetIt.instance; void setupLocator() { locator.registerFactory(() => AppInfo()); }
locator.dart 안에 GetIt의 instance를 저장하고 해당 파일을 import한 어디에서든 전역적으로 접근가능하도록 한다.
setupLocator라는 function을 생성하여 액세스하고자 하는 모든 type을 등록할 수 있다
type은 Factory와 Singleton 두 가지 방법으로 등록될 수 있다.
Factory method pattern
팩토리 메소드 패턴에서는 객체를 생성하기 위한 인터페이스를 정의하는데, 어떤 클래스의 인스턴스를 만들지는 서브클래스에서 결정하여 만든다. 따라서 해당 클래스의 인스턴스를 매번 새로 만들 필요가 없다.
Singleton
최초 한번만 메모리를 할당하고(Static) 그 메모리에 인스턴스를 만들어 사용하는 디자인패턴.
해당 인스턴스가 필요할 때 인스턴스를 새로 생성하는 것이 아니라 동일한 인스턴스를 사용하게 한다.- main.dart
import 'service_locator.dart'; void main() { setupLocator(); runApp(MyApp()); }
모든 타입들은 앱을 시작하기 전에 등록되어야한다.
setupLocator를 runApp() 전에 불러주면 service provider가 locator에 등록된다.
- 데이터 접근
import 'service_locator.dart'; ... @override Widget build(BuildContext context) { // Request the AppInfo from the locator var appInfo = locator<AppInfo>(); return Scaffold( body: Center( child: Text(appInfo.welcomeMessage), ), ); }
get it에서 instance를 요청하기 위해서는 widget을 감쌀 필요 없이
service_locator.dart의 locator로부터 AppInfo의 유형을 확인하면 된다.
따라서 전체적인 앱의 UI 구조가 변경되더라도 올바른 서비스와 값을 주입할 수 있다.
4) provider
InheritedWidget와 비슷하다.
특징
- 한 방향의 데이터 흐름을 강제한다.
- StateManagement에 최고다.
- 모든 공급자를 disposing할 수 있는 방법을 제공한다.
- 단 BuildContext가 없는 개체에 주입하기 위해서는 ProxyProvider를 많이 써야하는 번거로움이 있다.
사용예시
dependencies:
provider:^4.3.3- provider 생성
class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return Provider( builder: (context) => AppInfo(), child: MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: Scaffold(), ), ); } }
InheritedWidget처럼 Provider의 주입된 값은 하위 트리에서만 사용할 수 있다.
데이터를 어디에서든 사용할 수 있도록 전체 앱을 provider로 감싼다.
- 데이터 접근
@override Widget build(BuildContext context) { var appInfo = Provider.of<AppInfo>(context); return Scaffold( body: Center( child: Text(appInfo.welcomeMessage), ), ); }
하위 클래스에서 provider로 부터 접근하고자 하는 AppInfo에 접근할 수 있다.
인용 및 참고
- 종속성 주입
ichi.pro/ko/flutter-gandanhan-jongsogseong-ju-ib-laibeuleoli-60349278189554
api.flutter.dev/flutter/widgets/InheritedWidget-class.html
medium.com/manabie/how-does-flutter-inheritedwidget-work-3123f9d74c15
- factory 개념
- 싱글톤 개념
반응형'웹 개발 > Flutter' 카테고리의 다른 글
[Flutter Web] Fluro 패키지로 네비게이션 라우팅하기 ( Deep link / Transitions) (1) 2021.02.11 [Web] 안드로이드 스튜디오에서 웹 실행시 포트 지정(설정) 하는 법 (0) 2021.02.07 [Flutter] Service locator pattern을 사용해 build context 없이 navigate 하기 (0) 2021.01.31 [Flutter] 플러터 웹 사이트 반응형으로 생성하기 (1) 2021.01.23 [Flutter] 플러터 웹 프로젝트 생성 (0) 2021.01.23