Flutter Provider package

JJ·2023년 3월 6일
0

Flutter

목록 보기
3/3
post-thumbnail

아래와 같은 앱에서 TasksScreen state를 ListTile로 보내려면, 쓰건 안 쓰건 무조건 TaskList와 TaskTile에도 전달해주고 rebuild 해줘야 비로소 ListTile에서 사용할 수 있다.

이렇게 전달해주는 방식 말고 간단히 TasksScreen에서 바로 ListTile로 보내는 것을 가능하게 해주는 패키지가 provider 패키지라고 한다.

provider package
provider package 한국어 소개 & 사용법

provider 패키지를 사용해보기 위해 새로운 프로젝트를 생성했다.

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  final String data = 'Data';

  
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: TextWidget(data),
        ),
        body: Column(
          children: [
            UpperTextWidget(data),
          ],
        ),
      ),
    );
  }
}

class TextWidget extends StatelessWidget {
  final String data;
  TextWidget(this.data);

  
  Widget build(BuildContext context) {
    return Text(
      data,
      style: TextStyle(
        fontWeight: FontWeight.bold,
      ),
    );
  }
}

class UpperTextWidget extends StatelessWidget {
  final String data;
  UpperTextWidget(this.data);

  
  Widget build(BuildContext context) {
    return Padding(
      padding: EdgeInsets.all(10.0),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          TextField(),
          SizedBox(
            height: 10.0,
          ),
          OtherTextWidget(data),
        ],
      ),
    );
  }
}

class OtherTextWidget extends StatelessWidget {
  final String data;
  OtherTextWidget(this.data);

  
  Widget build(BuildContext context) {
    return Text(
      data,
      style: TextStyle(
        fontSize: 20.0,
      ),
    );
  }
}

대충 이렇게 생긴 앱이다.

MyApp 의 데이터인 final String data;를 파란색 OtherTextWidget에 전달해주기 위해 data가 필요치 않은 초록색 TextUpperWidget에도 생성자를 만들어 data를 받아오고 rebuild 되어야한다.

provider를 사용하면, 이런 불필요한 rebuild 과정이 생략되고 바로 데이터를 보낼 수 있다.

provider 사용법은 다음과 같다.

우선 사용을 위해 pubspec.yaml 파일의 dependencies에 provider를 추가해준다.

위에 뜨는 pubget을 눌러주고

provider를 import 해준다.

그리고 state를 사용하고자 하는 제일 높은 위젯을 provider로 감싸준다.

이 경우 MyApp이므로 MyApp을 감쌌다.

만약 데이터가 TextUpperWidget에 존재하고 그 하위의 위젯들에서만 사용될 것이라면 TextUpperWidget만 감싸면 된다.

공식문서를 읽어보니, Provider로 감싼 후에 아래와 같이 사용하라고 한다.

나의 경우 create: (_) => MyApp(), 를 넣어주면 된다.

그 후 value를 읽기위해 아래 공식문서에 나와있는 것처럼 context.watch<T>() 혹은 context.read<T>() 를 사용하면 된다.
(혹은 static method인 Provider.of<T>(context) 또는 Provider.of<T>(context, listen: false)를 사용하면 된다고 한다.)

watch는 위젯이 변화를 감지하게 하고 read는 변화를 감지하지 않는다고 한다.

이제 불필요한 생성자들을 모두 삭제하고 data가 사용되는 TextWidgetOtherTextWidget 에 우리가 사용할 data, 즉 context.watch<MyApp>().data를 넣어주면 된다.

data 를 잘 읽고있는지 확인하기 위해 data 의 값을 'Data' 에서 'Provided Data'로 바꿔봤다.
둘 다 잘 바뀌는 모습을 확인할 수 있었다.

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  final String data = 'Provided Data';

  
  Widget build(BuildContext context) {
    return Provider(
      create: (_) => MyApp(),
      child: MaterialApp(
        home: Scaffold(
          appBar: AppBar(
            title: TextWidget(),
          ),
          body: Column(
            children: [
              UpperTextWidget(),
            ],
          ),
        ),
      ),
    );
  }
}

class TextWidget extends StatelessWidget {
//불필요한 생성자가 사라졌다.
  
  Widget build(BuildContext context) {
    return Text(
      context.read<MyApp>().data,
      style: TextStyle(
        fontWeight: FontWeight.bold,
      ),
    );
  }
}

class UpperTextWidget extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return Padding(
      padding: EdgeInsets.all(10.0),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          TextField(),
          SizedBox(
            height: 10.0,
          ),
          OtherTextWidget(),
        ],
      ),
    );
  }
}

class OtherTextWidget extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return Text(
      context.read<MyApp>().data, // MyApp에서 바로 전달되는 모습
      style: TextStyle(
        fontSize: 20.0,
      ),
    );
  }
}

이제 바뀌는 것을 확인했으니, Input에 넣은 값을 data에 저장해볼것이다.

Provider는 고정된 하나의 값을 사용하는 것에 적합하지만, 바뀌는 값에는 적합하지 않다.
따라서 감싸줬던 ProviderChangeNotifierProvider<T>로 바꿔주었다.

바뀔 값도 따로 class로 빼준 뒤 with ChangeNotifier를 덧붙여야한다.
이는 notifyListenders();를 사용하기 위함인데, data를 받는 위젯들에게 notifyListeners();를 통해 값이 변했음을 알려줘야 값이 제대로 바뀌기 때문이다.

또한 위와 같은 이유로 보통 onChanged 함수에서 쓰듯이 간단히 data = newValue만을 적게되더라도 화면엔 아무런 변화도 나타나지 않을것이다.
notifyListeners();를 통해 변화를 듣고있는 위젯들에게 변화가 생겼다고 알려줘야만 값이 바뀌기 때문이다.

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return ChangeNotifierProvider<Data>(  //Provider 에서 ChangenotifierProvider로 바꿔줬다. 
      create: (_) => Data(), // data가 새로운 class인 Data에 들어있으므로 Data로 바꿔준다.
      child: MaterialApp(
        home: Scaffold(
          appBar: AppBar(
            title: TextWidget(),
          ),
          body: Column(
            children: [
              UpperTextWidget(),
            ],
          ),
        ),
      ),
    );
  }
}

class Data with ChangeNotifier {
  String data = 'Provided Data';  // 변하지 않는 값이 아니므로 final을 제거해준다.

  void changeValue(newValue) {
    data = newValue;
    notifyListeners();
  }
}

class TextWidget extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return Text(
      context.watch<Data>().data,  // 가져오는 값이 변하기에 watch 사용
      style: TextStyle(
        fontWeight: FontWeight.bold,
      ),
    );
  }
}

class UpperTextWidget extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return Padding(
      padding: EdgeInsets.all(10.0),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          TextField(
            onChanged: (newValue) {
              context.read<Data>().changeValue(newValue); // 변하지 않는 값이기에 read 사용
            },
          ),
          SizedBox(
            height: 10.0,
          ),
          OtherTextWidget(),
        ],
      ),
    );
  }
}

class OtherTextWidget extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return Text(
      context.watch<Data>().data, // 가져오는 값이 변하기에 watch 사용
      style: TextStyle(
        fontSize: 20.0,
      ),
    );
  }
}

위의 코드는 아래와 같이 잘 동작한다.

일단 현재 필요한 Provider 공부는 이정도니까...

오늘은 여기서 끝이다!

profile
신규...개발자가...되자...

0개의 댓글