본문 바로가기

Flutter/00 초보때 작성글

[Flutter] Bloc, Stream - setState 을 Provider 로 변경

Provider 강의 (www.youtube.com/watch?v=ikV-Ub9XgO4&list=PLwUg6hFuXV86arSYNF9x_5Vm_lKdIBpf9&index=4)

Base code (www.youtube.com/watch?v=2iWJRAcEsaQ&list=PLwUg6hFuXV86arSYNF9x_5Vm_lKdIBpf9&index=53)

Provider 강의 소스와 Base code 가 다릅니다.

 

Base code 영상의 최종소스를 기반으로 Provider 강의 설명을 바탕으로 setState ==> provider 로 변경.

 

<신규> saved_notifier.dart 코드 - 데이터 상태관리

 

import 'package:english_words/english_words.dart';
import 'package:flutter/foundation.dart';


class SavedNotifier extends ChangeNotifier{
  final Set<WordPair> _saved = Set<WordPair>();

  void toggleSaved(WordPair newSaved) {
    final bool alreadySaved = _saved.contains(newSaved);

    if (alreadySaved)
      _saved.remove(newSaved);
    else
      _saved.add(newSaved);
    notifyListeners(); // 변경시 알람을 전송하는 역할
  }

  bool alreadyContain(WordPair pair){
    return _saved.contains(pair);
  }

  Set<WordPair> get saved => _saved;

}

 

<수정> main.dart 코드

 

import 'package:bloc_stream/src/random_list.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'src/saved_notifier.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {

    return ChangeNotifierProvider<SavedNotifier>( // 4.0 이후부터 create 사용.
      create: (context) => SavedNotifier(),
      child: MaterialApp(
        home: RandomList(),
      ),
    );
  }
}

 

<수정> random_list.dart 코드

 

import 'package:bloc_stream/src/saved.dart';
import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';
import 'package:provider/provider.dart';
import 'saved_notifier.dart';

class RandomList extends StatefulWidget {
  @override
  _RandomListState createState() => _RandomListState();
}

class _RandomListState extends State<RandomList> {
  final List<WordPair> _suggestions = <WordPair>[];
//  final Set<WordPair> _saved = Set<WordPair>(); // provider 로 처리

  @override
  Widget build(BuildContext context) {

    return Scaffold(
      appBar: AppBar(
        centerTitle: true,
        title: Text('Naming App'),
        actions: <Widget>[
          IconButton(icon: Icon(Icons.list), onPressed: () {
          Navigator.of(context).push(
            MaterialPageRoute(builder: (context) => SavedList())
          ).then((value) {
            //저장화면에서 돌아오면 변경사항을 다시 그리기
            // Future/async/await 처리 안해도 됨.
            // 리턴시 처리 필요없음 return Future.value(true);
//            setState(() {
//              print('리턴시 setState');
//            });
          });
        })],
      ),
      body: _buildList(),
    );
  }

  Widget _buildList() {
    return ListView.builder(itemBuilder: (context, index) {
      // 0,2,4,6,8 is real items
      // 1,3,5,7,9 is dividers
      if (index.isOdd) {
        return Divider();
      }
      // 몫을 구하는 방법, 파이썬은 // 2개로처리.
      var realIndex = index ~/ 2;
      if (realIndex >= _suggestions.length) {
        // 화면에 표시할 내용이 단어 개수보다 많으면(같거나) 단어를 10 추가 생성할것.
        _suggestions.addAll(generateWordPairs().take(10));
      }
      return _buildRow(_suggestions[realIndex]);
    });
  }

  Widget _buildRow(WordPair pair) {
    // 해당 단어가 저장되어 있는지 확인
//    final bool alreadySaved = _saved.contains(pair); // 아래로 이동.

    return Consumer<SavedNotifier>( // Consumer 로 ListTile 감싸기
      builder: (context, SavedNotifier, child){
        bool alreadySaved = SavedNotifier.saved.contains(pair); // 이걸로 해도 되고,
        // bool alreadySaved = SavedNotifier.alreadyContain(pair) // 이걸로 해도 됨

        return ListTile(
          title: Text(
          pair.asPascalCase,
            textScaleFactor: 1.5,
          ),
          trailing: Icon(
            alreadySaved ? Icons.favorite : Icons.favorite_border,
            color: alreadySaved ? Colors.pinkAccent : null,
          ),
          onTap: () {
            SavedNotifier.toggleSaved(pair);
//            setState(() { // SavedNotifier.toggleSaved(pair) 로 대체함
//              if (alreadySaved)
//                SavedNotifier.saved.remove(pair);
//              else
//                SavedNotifier.saved.add(pair);
//              print(SavedNotifier.saved.toString());
//            });
          },
        );
      },
//        child: , // ListTile() 부분은 builder 내부로 이동
    );
  }
}

 

<수정> saved.dart

 

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

class SavedList extends StatefulWidget {
// 'saved' 리스트는 레퍼런스로 가져오기 때문에 여기서 삭제 가능
//  final Set<WordPair> saved;
//  SavedList({@required this.saved});

  @override
  _SavedListState createState() => _SavedListState();
}

class _SavedListState extends State<SavedList> {

  @override
  Widget build(BuildContext context) {
    final savedNotifier = context.watch<SavedNotifier>(); // 상위 위젯에서 데이터 찾기

    return Scaffold(
      appBar: AppBar(
        centerTitle: true,
        title: Text('Saved'),
      ),
      body: _buildList(savedNotifier.saved),
    );
  }

  Widget _buildList(Set<WordPair> saved) {

    return ListView.builder(
        itemCount: saved.length * 2,
        itemBuilder: (context, index) {
          if (index.isOdd) return Divider();
          var realIndex = index ~/ 2;
          return _buildRow(saved.toList()[realIndex], saved);
        });
  }

  Widget _buildRow(WordPair pair, Set<WordPair> saved) {
    return ListTile(
      title: Text(
        pair.asPascalCase,
        textScaleFactor: 1.5,
      ),
      onTap: () {
        Provider.of<SavedNotifier>(context, listen: false).toggleSaved(pair);
//        setState(() { // Provider.of 로 대체
//          // 'saved' 리스트는 레퍼런스로 가져오기 때문에 여기서 삭제 가능
//          saved.remove(pair);
//        });
      },
    );
  }
}