본문 바로가기

Flutter/00 Legacy

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

원본영상(www.youtube.com/watch?v=EKwVNTyRPq0&list=PLwUg6hFuXV86arSYNF9x_5Vm_lKdIBpf9&index=54)

 

AsIs : Scaffold >> ListView >> ListTile >> setState >> 재시작

ToBe : Scaffold >> StreamBuilder >> ListView >> ListTile >> bloc 메소드 >> StreamBuilder 

 

<신규> Bloc.dart

 

import 'dart:async'; // StreamController
import 'package:english_words/english_words.dart';

class Bloc {
  Set<WordPair> saved = Set<WordPair>();

  // StreamController 는 dispose 에서 close 해야 함.
  final _saveController = StreamController<Set<WordPair>>.broadcast(); // 동시 수신

//  Stream<Set<WordPair>> SavedStream() { // 아래 함수명으로 변경가능함
  get savedStream {
    return _saveController.stream;
  }

  // Stream 으로 데이터를 한번 보내주기, 수신하는곳은 모두 받음.
  get addCurrentSaved => _saveController.sink.add(saved);

  addToOrRemoveFromSavedList(WordPair item){
    // 이미 Set 에 있다면 Set 에서 제거
    if(saved.contains(item)){
      saved.remove(item);
    } else {
      // 이미 Set 에 없다면 Set 에서 추가
      saved.add(item);
    }
    // Stream 에 변경 사항을 전달
    _saveController.sink.add(saved);
  }

  dispose() {
    _saveController.close();
  }

}

var bloc = Bloc();

 

<수정> randdom_list.dart

 

import 'package:bloc_stream/src/saved.dart';
import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';
import 'bloc/Bloc.dart'; // Bloc 패턴 추가

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

class _RandomListState extends State<RandomList> {
  final List<WordPair> _suggestions = <WordPair>[];
//  final Set<WordPair> _saved = Set<WordPair>(); // Stream 으로 대체

  @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()) // bloc 사용(saved: _saved 삭제)
          ).then((value) { // 여기선 이부분도 주석처리해도 무방함.
            //저장화면에서 돌아오면 변경사항을 다시 그리기
            // Future/async/await 처리 안해도 됨.
            // 리턴시 처리 필요없음 return Future.value(true);
//            setState(() {
//            });
          });
        })],
      ),
      body: _buildList(),
    );
  }

  Widget _buildList() {
    return StreamBuilder<Set<WordPair>>( // StreamBuilder 로 ListView.builder 감싸기
      stream: bloc.savedStream, // Stream 연결
      builder: (context, snapshot) {
        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(snapshot.data, _suggestions[realIndex]);
//          return _buildRow(_suggestions[realIndex]);
        });
      }
    );
  }

//  Widget _buildRow(WordPair pair) {
  Widget _buildRow(Set<WordPair> saved, WordPair pair) {
    // 해당 단어(Set<WordPair> saved)가 저장되어 있는지 확인하고 아이콘 결정
    // 글로벌 변수대신(_saved) 인자로 넘어오는(saved) 값 사용
//    final bool alreadySaved = saved.contains(pair); // _saved => saved
    final bool alreadySaved = saved==null ? false : saved.contains(pair);// _saved => saved

    return ListTile(
      title: Text(
        pair.asPascalCase,
        textScaleFactor: 1.5,
      ),
      trailing: Icon(
        alreadySaved ? Icons.favorite : Icons.favorite_border,
        color: Colors.pinkAccent,
      ),
      onTap: () {
        // setState 전체 대신 addToOrRemoveFromSavedList() 함수로 처리.
        bloc.addToOrRemoveFromSavedList(pair);
        print(saved.toString()); // 저장된 리스트 화면 출력
//        setState(() {
//          if (alreadySaved)
//            _saved.remove(pair);
//          else
//            _saved.add(pair);
//          print(_saved.toString());
//        });
      },
    );
  }
}

 

<수정> saved.dart

 

import 'package:english_words/english_words.dart';
import 'package:flutter/material.dart';
import 'bloc/Bloc.dart';


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

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

class _SavedListState extends State<SavedList> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        centerTitle: true,
        title: Text('Saved'),
      ),
      body: _buildList(),
    );
  }

  Widget _buildList() {
    return StreamBuilder<Set<WordPair>>( // StreamBuilder 로 ListView.builder 감싸기
      stream: bloc.savedStream, // Stream 연결
      builder: (context, snapshot) {
        // 데이터가 없으면 가져오기 시도
        if(!snapshot.hasData) {
          bloc.addCurrentSaved;
          return Center(child: CircularProgressIndicator());
        }

//        var saved = snapshot.data ; // 아래에서 snapshot 을 직접사용함
        return ListView.builder(
            itemCount: snapshot.data.length * 2,
            itemBuilder: (context, index) {
              if (index.isOdd) return Divider();
              var realIndex = index ~/ 2;
              return _buildRow(snapshot.data.toList()[realIndex]);
            });
      }
    );
  }

  Widget _buildRow(WordPair pair) {
    return ListTile(
      title: Text(
        pair.asPascalCase,
        textScaleFactor: 1.5,
      ),
      onTap: () {
        // setState 전체 대신 addToOrRemoveFromSavedList() 함수로 처리.
        bloc.addToOrRemoveFromSavedList(pair);
        print(pair.toString()); // 제거된 단어(클릭된 단어) 출력
        
//        setState(() {
//          // 'saved' 리스트는 레퍼런스로 가져오기 때문에 여기서 삭제 가능
//          widget.saved.remove(pair);
//        });
      },
    );
  }
}