본문 바로가기

코딩/플러터

간단한 플러터 앱 - 2초마다 한번씩 랜덤한 이름 만들기





플러터를 처음 공부하게 되면 하게 되는 튜토리얼의 앱인

 

"랜덤 이름 제네레이터"

 

구글 코드 랩에 나와있는 이 앱을 간단하게 개선해보면서 Stream의 사용법을 익혀봅시다.

 

https://codelabs.developers.google.com/codelabs/first-flutter-app-pt2/#8

 

이 링크를 통해 "랜덤 이름 제네레이터"를 만들어 보았다는 가정 하에 글을 써보겠습니다.

 

자 그럼, 우리는 이 앱을 기반으로 기능 하나를 추가해 보도록 합시다.

 

추가해볼 기능은 바로, 제목에도 나와있듯이, 랜덤한 이름을 2초마다 한번씩 제네레이팅하는 기능입니다.

 

일단, Dart의 Stream에 대해 알아보기 전에, https://software-creator.tistory.com/13?category=681555 <에드신님께서 쓰신 해당 블로그 글을 읽어보고 Stream에 대해 먼저 알아보는 것도 좋은 방법이라고 생각합니다.

 

Dart의 Stream은 Rx프로그래밍의 Observable과 닮아있다고 생각합니다.

 

한쪽에서는 데이터를 만들고, 한쪽에서는 데이터를 소비하고..

 

이러한 Stream을 이용해서 "2초마다 한번씩 이름 만들기" 라는 기능과 섞으면 구현 완료입니다.

 

코드는 https://github.com/5seunghoon/Flutter_generate_random_name 여기에 있습니다.




첫번째. Stream 만들기

우리가 만들 스트림은 "2초마다 한 번씩 데이터 생산" 하는 스트림입니다.

 

그걸 위해, 


final Stream<List<WordPair>> _timeStreamImprove =
Stream.periodic(Duration(seconds: 2), (var i) {
suggestions.add(generateSingleWordPair);
return suggestions;
});

이렇게 Stream.periodic을 이용하여 2초마다 한 번씩 suggestion에 새로운 wordpair를 생산해서 추가하는 작업을 수행하면 됩니다.

 

여기서 suggestion은 기존 코드랩의 코드와 다르게 전역변수로 선언해놓아, State를 새로 만들어도 suggestion이 초기화되지 않게했습니다.

 

또한 generateSingleWordPair는



WordPair get generateSingleWordPair => generateWordPairs().take(1).toList()[0];


이러한 getter를 전역변수로 선언해놓아 활용하였습니다.

 

자 이제 _timeStreamImprove는 2초마다 한 번씩 새로운 wordPair가 추가된 suggetion을 뱉어냅니다.





두번째. Stream 소비하기

이제 이렇게 만들어진 스트림을 소비해봅시다.

 

Stream이 업데이트 되면, 그걸 잡아서 UI를 업데이트 해주면 됩니다.



Flexible _buildSuggestions() {
return Flexible(
child: StreamBuilder(
stream: _timeStreamImprove,
initialData: <WordPair>[generateSingleWordPair],
builder: (BuildContext context, AsyncSnapshot snapshot) {
suggestions.forEach((var f) {
print("s : " + f.asPascalCase);
});
print("${snapshot.data.runtimeType}");

return Scrollbar(
child: ListView.builder(
itemCount: suggestions.length * 2,
padding: const EdgeInsets.all(16.0),
itemBuilder: (context, i) {
if (i.isOdd) return Divider();
final index = i ~/ 2;
return _buildRow(suggestions[index]);
},
),
);
},
),
);
}


기존 코드의 _buildSuggestion을 이렇게 바꿉니다.

 

 

먼저 위젯 설명부터 하면, 

 

기존 코드와 다르게 Flexible을 리턴하는데, 이건 '남은 공간을 채워주는 유연한 위젯'입니다. 옵션으로 flex를 1로 주면 layout_weight와 같은 효과를 가지게 되죠.


이 Flexible의 child로는 StreamBuilder가 들어갑니다. 그리고 그러한 StreamBuilder의 속성으로 방금 만든 Stream이 할당되고, builder로 Scrollbar가 리턴됩니다. 


그리고 그 Scrollbar는 suggestion이 기준이 되는 ListView를 child로 가지고 있습니다.

 



이제 코드 설명을 해보겠습니다.

 

AsyncSnapshot을 문서에서 보면, 

 

Immutable representation of the most recent interaction with an asynchronous computation. 

 

라는 설명이 있습니다. 즉 비동기 연산의 가장 최근 결과를 나타낸다고 합니다. 

 

그래서 Stream에서 데이터를 만들어내면, AsyncSnapshot snapShot이 가장 최근의 데이터를 잡아냅니다.

 

여기서 이 snapShot.data는 타입이 dynamic입니다. 그래서 snapShot.data.runtimeType을 출력하면 List<WordPair>가 나올 것입니다.

 

즉 이 snapShot.data를 이용하면 ListView를 만들수 있습니다.






세번째. 완성

그리고 이러한 _buildSuggestion()을 코드랩의 코드처럼 StatefulWidget의 State의 Build에 넣으면 됩니다.\



class RandomWordsState extends State<RandomWords> {
final _saved = Set<WordPair>();
final _biggerFont = const TextStyle(fontSize: 18, letterSpacing: 6);

final Stream<List<WordPair>> _timeStreamImprove =
Stream.periodic(Duration(seconds: 2), (var i) {
suggestions.add(generateSingleWordPair);
return suggestions;
});

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Rand name generator"),
actions: <Widget>[ // Add 3 lines from here...
IconButton(icon: Icon(Icons.list), onPressed: _pushSaved),
],
),
body: Column(
children: [
_buildSuggestions(),
],
));
}


이제 실행하면, 이렇게 2초마다 한 번씩 새로운 이름이 생성되는 것을 발견할 수 있습니다.