SlideShare a Scribd company logo
1 of 52
상태관리 올바르게
쓰기
양수장
GDG Songdo Organizer (2020~ )
Flutter Songdo Organizer (2022 ~ )
Tech Lead @bluefrog (2020 ~ )
Android
Flutter
Back-end + DevOps
I’m an App Developer.
Bio
yangsterchief@duck.com
https://github.com/yangster-chief
https://www.linkedin.com/in/sujang-yang
GDG 인천 & 송도의 새로운 행사 소식은
카톡 오픈채팅방에서 :D
GDG 인천, 송도로 검색! 혹은 QR로 :)
카카오톡 오픈 채팅
https://open.kakao.com/o/gTplSH6
10월 21일 오후 1시
Machine Learning Meetup In Songdo
https://festa.io/events/4014
Flutter Songdo meetup
https://www.meetup.com/ko-KR/flutter-songdo
Flutter Seoul meetup
https://www.meetup.com/ko-KR/flutter-seoul/
Agenda
2 3
1
라이브러리를
이용한 상태관리
Flutter UI의
기본적인 이해
상태관리의
Best Practices
동적인 앱 구현
복잡한 UI 구현
코드의 가독성과 유지보수성
성능 최적화
앱의 확장성
+
+
+
Intro - 상태관리가 필요한 이유
+
+
Flutter UI의
기본적인 이해
// Imperative style
b.setColor(red)
b.clearChildren()
ViewC c3 = new
ViewC(...)
b.add(c3)
// Declarative style
return ViewB(
color: red,
child: const ViewC(),
);
var stars = Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.star, color: Colors.green[500]),
const Icon(Icons.star, color: Colors.black),
const Icon(Icons.star, color: Colors.black),
],
);
final ratings = Container(
padding: const EdgeInsets.all(20),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
stars,
const Text(
'170 Reviews',
style: TextStyle(
color: Colors.black,
fontWeight: FontWeight.w800,
fontFamily: 'Roboto',
letterSpacing: 0.5,
fontSize: 20,
),
),
],
),
);
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State<MyHomePage> createState() =>
_MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) => Scaffold(
...
}
BuildContext?! | Decoding Flutter
BuildContext
SetState 메소드를 활용한 상태관리 단점
전역상태 관리
특정 위젯 내에서만 사용하는
상태를 다루는 곳에서
적합합니다. 뷰 전체나 앱
전체에 영향을 미치는 상태를
관리하는데 한계가 있습니다.
복잡한 상태 관리
페이지 내 여러 상태가
상호작용하는 복잡한 UI를
구성할 때, 코드 구성 또한
복잡해질 수 있습니다.
최적화 문제
setState 메소드는 관련된
위젯트리 전체를 재구성하기
때문에, 크고 복잡한
위젯트리에서는 불필요한
렌더링이 발생할 수 있습니다.
라이브러리를
이용한 상태관리
비즈니스 로직 컴포넌트(Bloc)의 약자로,
앱의 로직을 위젯 UI로부터 분리하여
관리하는 패턴
스트림을 사용하여 상태의 변화를
연속적으로 감지하고 처리하는 것이 가능
상태를 변경하는 단일 방법을 적용하여 앱
내의 상태 변경을 예측가능하게 만듦
Bloc
part 'ticker_event.dart';
part 'ticker_state.dart';
class TickerBloc extends Bloc<TickerEvent, TickerState> {
TickerBloc(Ticker ticker) : super(TickerInitial()) {
on<TickerStarted>(
(event, emit) async {...}, transformer: restartable());
on<_TickerTicked>((event, emit) =>
emit(TickerTickSuccess(event.tick)));
}
}
ticker_bloc.dart
on<TickerStarted>(
(event, emit) async {
await emit.onEach<int>(
ticker.tick(),
onData: (tick) => add(_TickerTicked(tick)),
);
emit(const TickerComplete());
},
transformer: restartable(),
);
ticker_bloc.dart
import 'dart:async';
/// Class which exposes a `tick` method to emit values periodically.
class Ticker {
/// Emits a new `int` up to 10 every second.
Stream<int> tick() {
return Stream.periodic(const Duration(seconds: 1), (x) => x).take(10);
}
}
ticker.dart
part of 'ticker_bloc.dart';
abstract class TickerEvent extends Equatable {
const TickerEvent();
@override
List<Object> get props => [];
}
...
class _TickerTicked extends TickerEvent {
const _TickerTicked(this.tick);
final int tick;
@override
List<Object> get props => [tick];
}
ticker_event.dart
part of 'ticker_bloc.dart';
abstract class TickerState extends Equatable {
const TickerState();
@override
List<Object> get props => [];
}
class TickerInitial extends TickerState {}
ticker_state.dart
class TickerTickSuccess extends TickerState {
const TickerTickSuccess(this.count);
final int count;
@override
List<Object> get props => [count];
}
class TickerComplete extends TickerState {
const TickerComplete();
}
ticker_state.dart
class TickerApp extends MaterialApp {
TickerApp({super.key}): super(
home: BlocProvider(
create: (_) => TickerBloc(Ticker()),
child: const TickerPage(),
),
);
}
main.dart
class TickerPage extends StatelessWidget {
const TickerPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Flutter Bloc with Streams')),
body: Center( ... ),
floatingActionButton: FloatingActionButton(
onPressed: () =>
context.read<TickerBloc>().add(const TickerStarted()),
tooltip: 'Start',
child: const Icon(Icons.timer),
),
);
}
}
main.dart
child: BlocBuilder<TickerBloc, TickerState>(
builder: (context, state) {
if (state is TickerTickSuccess) {
return Text('Tick #${state.count}');
}
if (state is TickerComplete) {
return const Text(
'Complete! Press the floating button to restart.',
);
}
return const Text('Press the floating button to start.');
},
),
main.dart
Flutter와 독립적이며, Dart 언어만을 위해
설계되었습니다.
Provider와는 달리 전역 접근 가능성, 타입
안정성, 유연성 등의 특징을 갖습니다.
riverpod
part 'main.g.dart';
void main() {
runApp(
const ProviderScope(child: MyApp()),
);
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(home: Home());
}
}
main.dart
@riverpod
class Counter extends _$Counter {
@override
int build() => 0;
void increment() => state++;
}
main.dart
class Home extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
return Scaffold(
appBar: AppBar(title: const Text('Counter example')),
body: Center(
child: Text('${ref.watch(counterProvider)}'),
),
floatingActionButton: FloatingActionButton(
onPressed: () => ref.read(counterProvider.notifier).increment(),
child: const Icon(Icons.add),
),
);
}
}
main.dart
@riverpod
Future<List<Package>> fetchPackages(
FetchPackagesRef ref, {
required int page,
String search = '',
}) async {
final dio = Dio();
// Fetch an API. Here we're using package:dio, but we could use anything else.
final response = await dio.get<List<Object?>>(
'https://pub.dartlang.org/api/search?page=$page&q=${Uri.encodeQueryComponent(search)}',
);
// Decode the JSON response into a Dart class.
return response.data?.map(Package.fromJson).toList() ?? const [];
}
상태관리의 Best
Practice
Immutable State
데이터 일관성 유지
상태가 예측 가능한
방식으로만 변경될 수
있습니다.
버그 감소
상태 변경으로 인한 부작용
(side-effect)을 줄일 수
있습니다.
개발 효율성 향상
상태 변경을 추적하기 쉬워
디버깅이 용이합니다.
Clear Separation of Concerns
가독성 향상
코드를 더 이해하기 쉬워지고,
관리가 용이합니다.
유지보수성 향상
각 모듈이 독립적으로
작동하므로, 한 부분의 변경이
다른 부분에 미치는 영향을
최소화합니다.
재사용성 향상
독립적으로 작동하는 모듈은
다른 프로젝트에서도 재사용이
가능합니다.
상태 관리를 통해 UI 로직과 비즈니스
로직을 분리할 수 있습니다.
Clear Separation of
Concerns
dog_images_bloc.dart
...
@injectable
class DogImagesBloc extends Bloc<DogImagesEvent, DogImagesState> {
DogImagesBloc(GetDogImagesUseCase getDogImagesUseCase, ...) :
_getDogImagesUseCase = getDogImagesUseCase, ...
super(const DogImagesState()) {
on<GetRemoteDogImagesEvent>(_onGetRemoteDogImages);
}
...
void _onGetRemoteDogImages(GetRemoteDogImagesEvent event, Emitter<DogImagesState> emit) async {
emit(const RemoteDogImagesLoading());
await _getDogImagesUseCase.call(
params: GetDogDto(isRemote: true, limit: event.limit),
onSuccess: (data) async => emit(RemoteDogImagesLoaded(data)),
onFailure: (error) async => emit(RemoteDogImagesError(error)),
);
}
}
dog_images_event.dart
sealed class DogImagesEvent extends Equatable {
const DogImagesEvent();
@override
List<Object> get props => [];
}
class GetRemoteDogImagesEvent extends DogImagesEvent {
final bool isForceUpdate;
final int limit;
const GetRemoteDogImagesEvent({this.isForceUpdate = true, this.limit = 10});
}
class GetLocalDogImagesEvent extends DogImagesEvent {
const GetLocalDogImagesEvent();
}
...
dog_images_state.dart
sealed class DogImagesState extends Equatable {
final List<DogImage> remoteImages;
final List<DogImage> localImages;
final AppError error;
const DogImagesState({
this.remoteImages = const [],
this.localImages = const [],
this.error = const AppError(),
});
@override
List<Object> get props => [remoteImages, localImages, error];
}
...
class RemoteDogImagesLoading extends DogImagesState {
const RemoteDogImagesLoading();
}
...
local_dog_card_page.dart
...
Widget build(BuildContext context) => BlocBuilder<DogImagesBloc, DogImagesState>(
builder: (context, state) {
if (state is LocalDogImagesLoading) {
return const LoadingDogCardFrame();
} else if (state is LocalDogImagesLoaded) {
final cards = state.localImages.map(DogImageCard.new).toList();
return LocalDogCardFrame(
items: state.localImages,
controller: controller,
onSwipe: _onSwipe,
cards: cards,
onClearAll: () {
context.read<DogImagesBloc>().add(const ClearDogImagesEvent());
},
onDelete: () {
...
dog_images_bloc.dart
...
@injectable
class DogImagesBloc extends Bloc<DogImagesEvent, DogImagesState> {
DogImagesBloc(GetDogImagesUseCase getDogImagesUseCase, ...) :
_getDogImagesUseCase = getDogImagesUseCase, ...
super(const DogImagesState()) {
on<GetRemoteDogImagesEvent>(_onGetRemoteDogImages);
}
...
void _onGetRemoteDogImages(GetRemoteDogImagesEvent event, Emitter<DogImagesState> emit) async {
emit(const RemoteDogImagesLoading());
await _getDogImagesUseCase.call(
params: GetDogDto(isRemote: true, limit: event.limit),
onSuccess: (data) async => emit(RemoteDogImagesLoaded(data)),
onFailure: (error) async => emit(RemoteDogImagesError(error)),
);
}
}
dog_images_bloc.dart
...
@injectable
class DogImagesBloc extends Bloc<DogImagesEvent, DogImagesState> {
DogImagesBloc(GetDogImagesUseCase getDogImagesUseCase, ...) :
_getDogImagesUseCase = getDogImagesUseCase, ...
super(const DogImagesState()) {
on<GetRemoteDogImagesEvent>(_onGetRemoteDogImages);
}
...
void _onGetRemoteDogImages(GetRemoteDogImagesEvent event, Emitter<DogImagesState>
emit) async {
emit(const RemoteDogImagesLoading());
await _getDogImagesUseCase.call(
params: GetDogDto(isRemote: true, limit: event.limit),
onSuccess: (data) async => emit(RemoteDogImagesLoaded(data)),
onFailure: (error) async => emit(RemoteDogImagesError(error)),
);
}
}
local_dog_card_page.dart
...
Widget build(BuildContext context) => BlocBuilder<DogImagesBloc, DogImagesState>(
builder: (context, state) {
if (state is LocalDogImagesLoading) {
return const LoadingDogCardFrame();
} else if (state is LocalDogImagesLoaded) {
final cards = state.localImages.map(DogImageCard.new).toList();
return LocalDogCardFrame(
items: state.localImages,
controller: controller,
onSwipe: _onSwipe,
cards: cards,
onClearAll: () {
context.read<DogImagesBloc>().add(const ClearDogImagesEvent());
},
onDelete: () {
...
local_dog_card_page.dart
return Container(
clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(
...
color: Colors.white,
boxShadow: [
...
Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
item.breeds.isNotEmpty ?
item.breeds[0].name : 'No Name',
style: const TextStyle(color: Colors.black,
fontWeight: FontWeight.bold, fontSize: 20)),
const SizedBox(height: 5),
...
Flutter
Flutter 에
Clean Architecture 를
얹어보자 - 2/e
THANK
YOU!
v
yangsterchief@duck.com
https://github.com/yangster-chief
https://www.linkedin.com/in/sujang-yang

More Related Content

What's hot

Zagor lib kb 001 duh sa sjekirom
Zagor lib kb 001   duh sa sjekiromZagor lib kb 001   duh sa sjekirom
Zagor lib kb 001 duh sa sjekiromStripovizijacom
 
Python Programming ADP VTU CSE 18CS55 Module 5 Chapter 4
Python Programming ADP VTU CSE 18CS55 Module 5 Chapter 4Python Programming ADP VTU CSE 18CS55 Module 5 Chapter 4
Python Programming ADP VTU CSE 18CS55 Module 5 Chapter 4Demian Antony DMello
 
ZS - 0163 - Teks Viler - LOV NA LISICE
ZS - 0163 - Teks Viler - LOV NA LISICEZS - 0163 - Teks Viler - LOV NA LISICE
ZS - 0163 - Teks Viler - LOV NA LISICEStripovizijacom
 
Жизненный цикл производства курсов: чему корпоративное обучение может научить...
Жизненный цикл производства курсов: чему корпоративное обучение может научить...Жизненный цикл производства курсов: чему корпоративное обучение может научить...
Жизненный цикл производства курсов: чему корпоративное обучение может научить...Marina Litvinova
 
0725. VELIKA PLJAČKA
0725. VELIKA PLJAČKA0725. VELIKA PLJAČKA
0725. VELIKA PLJAČKATompa *
 
スマホ Web 版トップページの事例で学ぶ デザインシステム導入の過程で意識すべきポイント #yjtc / YJTC C-9
スマホ Web 版トップページの事例で学ぶ デザインシステム導入の過程で意識すべきポイント #yjtc / YJTC C-9スマホ Web 版トップページの事例で学ぶ デザインシステム導入の過程で意識すべきポイント #yjtc / YJTC C-9
スマホ Web 版トップページの事例で学ぶ デザインシステム導入の過程で意識すべきポイント #yjtc / YJTC C-9Yahoo!デベロッパーネットワーク
 
TTF.RID.09
TTF.RID.09TTF.RID.09
TTF.RID.09Arcee327
 
Zs 0944 zagor - na granici stvarnog (scanturion &amp; folpi &amp; emeri)(6 mb)
Zs 0944   zagor - na granici stvarnog (scanturion &amp; folpi &amp; emeri)(6 mb)Zs 0944   zagor - na granici stvarnog (scanturion &amp; folpi &amp; emeri)(6 mb)
Zs 0944 zagor - na granici stvarnog (scanturion &amp; folpi &amp; emeri)(6 mb)zoran radovic
 
Zs 1019 zagor - izmedju dve vatre (scanturion &amp; folpi &amp; emeri)(5 mb)
Zs 1019   zagor - izmedju dve vatre (scanturion &amp; folpi &amp; emeri)(5 mb)Zs 1019   zagor - izmedju dve vatre (scanturion &amp; folpi &amp; emeri)(5 mb)
Zs 1019 zagor - izmedju dve vatre (scanturion &amp; folpi &amp; emeri)(5 mb)zoran radovic
 
Computer certificate
Computer certificateComputer certificate
Computer certificatejohn maina
 
«Кыргызские узоры»
«Кыргызские узоры»«Кыргызские узоры»
«Кыргызские узоры»akipress
 
โบรชัวร์โปรโมชั่นคารฟูร์ รายเดือน 27สค 9 กย 53
โบรชัวร์โปรโมชั่นคารฟูร์ รายเดือน 27สค   9 กย 53โบรชัวร์โปรโมชั่นคารฟูร์ รายเดือน 27สค   9 กย 53
โบรชัวร์โปรโมชั่นคารฟูร์ รายเดือน 27สค 9 กย 53โปรโมชั่นทูยู
 
Singapore Math - 6B - Textbook
Singapore Math - 6B - TextbookSingapore Math - 6B - Textbook
Singapore Math - 6B - TextbookIrene Linsky
 
Zagor Ludens Almanah 004 - Krvavi ocčnjaci
Zagor Ludens Almanah 004 - Krvavi ocčnjaciZagor Ludens Almanah 004 - Krvavi ocčnjaci
Zagor Ludens Almanah 004 - Krvavi ocčnjaciStripovizijacom
 
Zikre Zamil By Allamah Shafee Okarvi
Zikre Zamil By Allamah Shafee OkarviZikre Zamil By Allamah Shafee Okarvi
Zikre Zamil By Allamah Shafee OkarviWajid Malik
 
Zagor Ludens 220 - Ugriz zmije
Zagor Ludens  220 - Ugriz zmijeZagor Ludens  220 - Ugriz zmije
Zagor Ludens 220 - Ugriz zmijeStripovizijacom
 

What's hot (20)

Zagor lib kb 001 duh sa sjekirom
Zagor lib kb 001   duh sa sjekiromZagor lib kb 001   duh sa sjekirom
Zagor lib kb 001 duh sa sjekirom
 
Python Programming ADP VTU CSE 18CS55 Module 5 Chapter 4
Python Programming ADP VTU CSE 18CS55 Module 5 Chapter 4Python Programming ADP VTU CSE 18CS55 Module 5 Chapter 4
Python Programming ADP VTU CSE 18CS55 Module 5 Chapter 4
 
Dr Ak - TW - LUD 93
Dr Ak - TW - LUD 93Dr Ak - TW - LUD 93
Dr Ak - TW - LUD 93
 
ZS - 0163 - Teks Viler - LOV NA LISICE
ZS - 0163 - Teks Viler - LOV NA LISICEZS - 0163 - Teks Viler - LOV NA LISICE
ZS - 0163 - Teks Viler - LOV NA LISICE
 
Жизненный цикл производства курсов: чему корпоративное обучение может научить...
Жизненный цикл производства курсов: чему корпоративное обучение может научить...Жизненный цикл производства курсов: чему корпоративное обучение может научить...
Жизненный цикл производства курсов: чему корпоративное обучение может научить...
 
0725. VELIKA PLJAČKA
0725. VELIKA PLJAČKA0725. VELIKA PLJAČKA
0725. VELIKA PLJAČKA
 
903 osveta crne ruke
903  osveta crne ruke903  osveta crne ruke
903 osveta crne ruke
 
スマホ Web 版トップページの事例で学ぶ デザインシステム導入の過程で意識すべきポイント #yjtc / YJTC C-9
スマホ Web 版トップページの事例で学ぶ デザインシステム導入の過程で意識すべきポイント #yjtc / YJTC C-9スマホ Web 版トップページの事例で学ぶ デザインシステム導入の過程で意識すべきポイント #yjtc / YJTC C-9
スマホ Web 版トップページの事例で学ぶ デザインシステム導入の過程で意識すべきポイント #yjtc / YJTC C-9
 
TTF.RID.09
TTF.RID.09TTF.RID.09
TTF.RID.09
 
Ğ hərfi
Ğ hərfiĞ hərfi
Ğ hərfi
 
Zs 0944 zagor - na granici stvarnog (scanturion &amp; folpi &amp; emeri)(6 mb)
Zs 0944   zagor - na granici stvarnog (scanturion &amp; folpi &amp; emeri)(6 mb)Zs 0944   zagor - na granici stvarnog (scanturion &amp; folpi &amp; emeri)(6 mb)
Zs 0944 zagor - na granici stvarnog (scanturion &amp; folpi &amp; emeri)(6 mb)
 
Zs 1019 zagor - izmedju dve vatre (scanturion &amp; folpi &amp; emeri)(5 mb)
Zs 1019   zagor - izmedju dve vatre (scanturion &amp; folpi &amp; emeri)(5 mb)Zs 1019   zagor - izmedju dve vatre (scanturion &amp; folpi &amp; emeri)(5 mb)
Zs 1019 zagor - izmedju dve vatre (scanturion &amp; folpi &amp; emeri)(5 mb)
 
Computer certificate
Computer certificateComputer certificate
Computer certificate
 
«Кыргызские узоры»
«Кыргызские узоры»«Кыргызские узоры»
«Кыргызские узоры»
 
950 tajna crvene ruke
950  tajna crvene ruke950  tajna crvene ruke
950 tajna crvene ruke
 
โบรชัวร์โปรโมชั่นคารฟูร์ รายเดือน 27สค 9 กย 53
โบรชัวร์โปรโมชั่นคารฟูร์ รายเดือน 27สค   9 กย 53โบรชัวร์โปรโมชั่นคารฟูร์ รายเดือน 27สค   9 กย 53
โบรชัวร์โปรโมชั่นคารฟูร์ รายเดือน 27สค 9 กย 53
 
Singapore Math - 6B - Textbook
Singapore Math - 6B - TextbookSingapore Math - 6B - Textbook
Singapore Math - 6B - Textbook
 
Zagor Ludens Almanah 004 - Krvavi ocčnjaci
Zagor Ludens Almanah 004 - Krvavi ocčnjaciZagor Ludens Almanah 004 - Krvavi ocčnjaci
Zagor Ludens Almanah 004 - Krvavi ocčnjaci
 
Zikre Zamil By Allamah Shafee Okarvi
Zikre Zamil By Allamah Shafee OkarviZikre Zamil By Allamah Shafee Okarvi
Zikre Zamil By Allamah Shafee Okarvi
 
Zagor Ludens 220 - Ugriz zmije
Zagor Ludens  220 - Ugriz zmijeZagor Ludens  220 - Ugriz zmije
Zagor Ludens 220 - Ugriz zmije
 

Similar to [Flutter Meetup In Songdo] 상태관리 올바르게 쓰기

안드로이드 개발자를 위한 스위프트
안드로이드 개발자를 위한 스위프트안드로이드 개발자를 위한 스위프트
안드로이드 개발자를 위한 스위프트병한 유
 
[114]angularvs react 김훈민손찬욱
[114]angularvs react 김훈민손찬욱[114]angularvs react 김훈민손찬욱
[114]angularvs react 김훈민손찬욱NAVER D2
 
[Google I_O Extended Daejeon 2023] 처음 시작하는 Flutter
[Google I_O Extended Daejeon 2023] 처음 시작하는  Flutter[Google I_O Extended Daejeon 2023] 처음 시작하는  Flutter
[Google I_O Extended Daejeon 2023] 처음 시작하는 FlutterSuJang Yang
 
Clean code
Clean codeClean code
Clean codebbongcsu
 
The roadtocodecraft
The roadtocodecraftThe roadtocodecraft
The roadtocodecraftbbongcsu
 
Software Development Process - Korean
Software Development Process - KoreanSoftware Development Process - Korean
Software Development Process - KoreanTerry Cho
 
NDC11_김성익_슈퍼클래스
NDC11_김성익_슈퍼클래스NDC11_김성익_슈퍼클래스
NDC11_김성익_슈퍼클래스Sungik Kim
 
20150212 c++11 features used in crow
20150212 c++11 features used in crow20150212 c++11 features used in crow
20150212 c++11 features used in crowJaeseung Ha
 
[XECon+PHPFest 2014] jQuery 개발자에서 AngularJS 개발자 되기
[XECon+PHPFest 2014] jQuery 개발자에서 AngularJS 개발자 되기[XECon+PHPFest 2014] jQuery 개발자에서 AngularJS 개발자 되기
[XECon+PHPFest 2014] jQuery 개발자에서 AngularJS 개발자 되기Jeado Ko
 
반복적인 작업이 싫은 안드로이드 개발자에게
반복적인 작업이 싫은 안드로이드 개발자에게반복적인 작업이 싫은 안드로이드 개발자에게
반복적인 작업이 싫은 안드로이드 개발자에게Sungju Jin
 
Design patterns
Design patternsDesign patterns
Design patternsdf
 
[1A5]효율적인안드로이드앱개발
[1A5]효율적인안드로이드앱개발[1A5]효율적인안드로이드앱개발
[1A5]효율적인안드로이드앱개발NAVER D2
 
[NDC17] Unreal.js - 자바스크립트로 쉽고 빠른 UE4 개발하기
[NDC17] Unreal.js - 자바스크립트로 쉽고 빠른 UE4 개발하기[NDC17] Unreal.js - 자바스크립트로 쉽고 빠른 UE4 개발하기
[NDC17] Unreal.js - 자바스크립트로 쉽고 빠른 UE4 개발하기현철 조
 
Domain Specific Languages With Groovy
Domain Specific Languages With GroovyDomain Specific Languages With Groovy
Domain Specific Languages With GroovyTommy C. Kang
 
StarUML NS Guide - Design
StarUML NS Guide - DesignStarUML NS Guide - Design
StarUML NS Guide - Design태욱 양
 
ReactJS | 서버와 클라이어트에서 동시에 사용하는
ReactJS | 서버와 클라이어트에서 동시에 사용하는ReactJS | 서버와 클라이어트에서 동시에 사용하는
ReactJS | 서버와 클라이어트에서 동시에 사용하는Taegon Kim
 
Design pattern 옵저버
Design pattern 옵저버Design pattern 옵저버
Design pattern 옵저버Sukjin Yun
 
#19.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)_국비지원IT학원/실업자/재직자환급교육/자바/스프링/...
#19.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)_국비지원IT학원/실업자/재직자환급교육/자바/스프링/...#19.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)_국비지원IT학원/실업자/재직자환급교육/자바/스프링/...
#19.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)_국비지원IT학원/실업자/재직자환급교육/자바/스프링/...탑크리에듀(구로디지털단지역3번출구 2분거리)
 
Tizen main loop 이해
Tizen main loop 이해Tizen main loop 이해
Tizen main loop 이해Hermet Park
 

Similar to [Flutter Meetup In Songdo] 상태관리 올바르게 쓰기 (20)

안드로이드 개발자를 위한 스위프트
안드로이드 개발자를 위한 스위프트안드로이드 개발자를 위한 스위프트
안드로이드 개발자를 위한 스위프트
 
[114]angularvs react 김훈민손찬욱
[114]angularvs react 김훈민손찬욱[114]angularvs react 김훈민손찬욱
[114]angularvs react 김훈민손찬욱
 
[Google I_O Extended Daejeon 2023] 처음 시작하는 Flutter
[Google I_O Extended Daejeon 2023] 처음 시작하는  Flutter[Google I_O Extended Daejeon 2023] 처음 시작하는  Flutter
[Google I_O Extended Daejeon 2023] 처음 시작하는 Flutter
 
Clean code
Clean codeClean code
Clean code
 
The roadtocodecraft
The roadtocodecraftThe roadtocodecraft
The roadtocodecraft
 
Software Development Process - Korean
Software Development Process - KoreanSoftware Development Process - Korean
Software Development Process - Korean
 
NDC11_김성익_슈퍼클래스
NDC11_김성익_슈퍼클래스NDC11_김성익_슈퍼클래스
NDC11_김성익_슈퍼클래스
 
20150212 c++11 features used in crow
20150212 c++11 features used in crow20150212 c++11 features used in crow
20150212 c++11 features used in crow
 
[XECon+PHPFest 2014] jQuery 개발자에서 AngularJS 개발자 되기
[XECon+PHPFest 2014] jQuery 개발자에서 AngularJS 개발자 되기[XECon+PHPFest 2014] jQuery 개발자에서 AngularJS 개발자 되기
[XECon+PHPFest 2014] jQuery 개발자에서 AngularJS 개발자 되기
 
react-ko.pdf
react-ko.pdfreact-ko.pdf
react-ko.pdf
 
반복적인 작업이 싫은 안드로이드 개발자에게
반복적인 작업이 싫은 안드로이드 개발자에게반복적인 작업이 싫은 안드로이드 개발자에게
반복적인 작업이 싫은 안드로이드 개발자에게
 
Design patterns
Design patternsDesign patterns
Design patterns
 
[1A5]효율적인안드로이드앱개발
[1A5]효율적인안드로이드앱개발[1A5]효율적인안드로이드앱개발
[1A5]효율적인안드로이드앱개발
 
[NDC17] Unreal.js - 자바스크립트로 쉽고 빠른 UE4 개발하기
[NDC17] Unreal.js - 자바스크립트로 쉽고 빠른 UE4 개발하기[NDC17] Unreal.js - 자바스크립트로 쉽고 빠른 UE4 개발하기
[NDC17] Unreal.js - 자바스크립트로 쉽고 빠른 UE4 개발하기
 
Domain Specific Languages With Groovy
Domain Specific Languages With GroovyDomain Specific Languages With Groovy
Domain Specific Languages With Groovy
 
StarUML NS Guide - Design
StarUML NS Guide - DesignStarUML NS Guide - Design
StarUML NS Guide - Design
 
ReactJS | 서버와 클라이어트에서 동시에 사용하는
ReactJS | 서버와 클라이어트에서 동시에 사용하는ReactJS | 서버와 클라이어트에서 동시에 사용하는
ReactJS | 서버와 클라이어트에서 동시에 사용하는
 
Design pattern 옵저버
Design pattern 옵저버Design pattern 옵저버
Design pattern 옵저버
 
#19.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)_국비지원IT학원/실업자/재직자환급교육/자바/스프링/...
#19.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)_국비지원IT학원/실업자/재직자환급교육/자바/스프링/...#19.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)_국비지원IT학원/실업자/재직자환급교육/자바/스프링/...
#19.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)_국비지원IT학원/실업자/재직자환급교육/자바/스프링/...
 
Tizen main loop 이해
Tizen main loop 이해Tizen main loop 이해
Tizen main loop 이해
 

[Flutter Meetup In Songdo] 상태관리 올바르게 쓰기

  • 2. GDG Songdo Organizer (2020~ ) Flutter Songdo Organizer (2022 ~ ) Tech Lead @bluefrog (2020 ~ ) Android Flutter Back-end + DevOps I’m an App Developer. Bio yangsterchief@duck.com https://github.com/yangster-chief https://www.linkedin.com/in/sujang-yang
  • 3. GDG 인천 & 송도의 새로운 행사 소식은 카톡 오픈채팅방에서 :D GDG 인천, 송도로 검색! 혹은 QR로 :) 카카오톡 오픈 채팅 https://open.kakao.com/o/gTplSH6
  • 4. 10월 21일 오후 1시 Machine Learning Meetup In Songdo https://festa.io/events/4014
  • 7. Agenda 2 3 1 라이브러리를 이용한 상태관리 Flutter UI의 기본적인 이해 상태관리의 Best Practices
  • 8. 동적인 앱 구현 복잡한 UI 구현 코드의 가독성과 유지보수성 성능 최적화 앱의 확장성 + + + Intro - 상태관리가 필요한 이유 + +
  • 10.
  • 11.
  • 13. // Declarative style return ViewB( color: red, child: const ViewC(), );
  • 14. var stars = Row( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.star, color: Colors.green[500]), const Icon(Icons.star, color: Colors.black), const Icon(Icons.star, color: Colors.black), ], ); final ratings = Container( padding: const EdgeInsets.all(20), child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ stars, const Text( '170 Reviews', style: TextStyle( color: Colors.black, fontWeight: FontWeight.w800, fontFamily: 'Roboto', letterSpacing: 0.5, fontSize: 20, ), ), ], ), );
  • 15.
  • 16.
  • 17.
  • 18.
  • 19. class MyHomePage extends StatefulWidget { const MyHomePage({super.key, required this.title}); final String title; @override State<MyHomePage> createState() => _MyHomePageState(); }
  • 20. class _MyHomePageState extends State<MyHomePage> { int _counter = 0; void _incrementCounter() { setState(() { _counter++; }); } @override Widget build(BuildContext context) => Scaffold( ... }
  • 21. BuildContext?! | Decoding Flutter BuildContext
  • 22. SetState 메소드를 활용한 상태관리 단점 전역상태 관리 특정 위젯 내에서만 사용하는 상태를 다루는 곳에서 적합합니다. 뷰 전체나 앱 전체에 영향을 미치는 상태를 관리하는데 한계가 있습니다. 복잡한 상태 관리 페이지 내 여러 상태가 상호작용하는 복잡한 UI를 구성할 때, 코드 구성 또한 복잡해질 수 있습니다. 최적화 문제 setState 메소드는 관련된 위젯트리 전체를 재구성하기 때문에, 크고 복잡한 위젯트리에서는 불필요한 렌더링이 발생할 수 있습니다.
  • 24. 비즈니스 로직 컴포넌트(Bloc)의 약자로, 앱의 로직을 위젯 UI로부터 분리하여 관리하는 패턴 스트림을 사용하여 상태의 변화를 연속적으로 감지하고 처리하는 것이 가능 상태를 변경하는 단일 방법을 적용하여 앱 내의 상태 변경을 예측가능하게 만듦 Bloc
  • 25. part 'ticker_event.dart'; part 'ticker_state.dart'; class TickerBloc extends Bloc<TickerEvent, TickerState> { TickerBloc(Ticker ticker) : super(TickerInitial()) { on<TickerStarted>( (event, emit) async {...}, transformer: restartable()); on<_TickerTicked>((event, emit) => emit(TickerTickSuccess(event.tick))); } } ticker_bloc.dart
  • 26. on<TickerStarted>( (event, emit) async { await emit.onEach<int>( ticker.tick(), onData: (tick) => add(_TickerTicked(tick)), ); emit(const TickerComplete()); }, transformer: restartable(), ); ticker_bloc.dart
  • 27. import 'dart:async'; /// Class which exposes a `tick` method to emit values periodically. class Ticker { /// Emits a new `int` up to 10 every second. Stream<int> tick() { return Stream.periodic(const Duration(seconds: 1), (x) => x).take(10); } } ticker.dart
  • 28. part of 'ticker_bloc.dart'; abstract class TickerEvent extends Equatable { const TickerEvent(); @override List<Object> get props => []; } ... class _TickerTicked extends TickerEvent { const _TickerTicked(this.tick); final int tick; @override List<Object> get props => [tick]; } ticker_event.dart
  • 29. part of 'ticker_bloc.dart'; abstract class TickerState extends Equatable { const TickerState(); @override List<Object> get props => []; } class TickerInitial extends TickerState {} ticker_state.dart
  • 30. class TickerTickSuccess extends TickerState { const TickerTickSuccess(this.count); final int count; @override List<Object> get props => [count]; } class TickerComplete extends TickerState { const TickerComplete(); } ticker_state.dart
  • 31. class TickerApp extends MaterialApp { TickerApp({super.key}): super( home: BlocProvider( create: (_) => TickerBloc(Ticker()), child: const TickerPage(), ), ); } main.dart
  • 32. class TickerPage extends StatelessWidget { const TickerPage({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Flutter Bloc with Streams')), body: Center( ... ), floatingActionButton: FloatingActionButton( onPressed: () => context.read<TickerBloc>().add(const TickerStarted()), tooltip: 'Start', child: const Icon(Icons.timer), ), ); } } main.dart
  • 33. child: BlocBuilder<TickerBloc, TickerState>( builder: (context, state) { if (state is TickerTickSuccess) { return Text('Tick #${state.count}'); } if (state is TickerComplete) { return const Text( 'Complete! Press the floating button to restart.', ); } return const Text('Press the floating button to start.'); }, ), main.dart
  • 34. Flutter와 독립적이며, Dart 언어만을 위해 설계되었습니다. Provider와는 달리 전역 접근 가능성, 타입 안정성, 유연성 등의 특징을 갖습니다. riverpod
  • 35. part 'main.g.dart'; void main() { runApp( const ProviderScope(child: MyApp()), ); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp(home: Home()); } } main.dart
  • 36. @riverpod class Counter extends _$Counter { @override int build() => 0; void increment() => state++; } main.dart
  • 37. class Home extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { return Scaffold( appBar: AppBar(title: const Text('Counter example')), body: Center( child: Text('${ref.watch(counterProvider)}'), ), floatingActionButton: FloatingActionButton( onPressed: () => ref.read(counterProvider.notifier).increment(), child: const Icon(Icons.add), ), ); } } main.dart
  • 38. @riverpod Future<List<Package>> fetchPackages( FetchPackagesRef ref, { required int page, String search = '', }) async { final dio = Dio(); // Fetch an API. Here we're using package:dio, but we could use anything else. final response = await dio.get<List<Object?>>( 'https://pub.dartlang.org/api/search?page=$page&q=${Uri.encodeQueryComponent(search)}', ); // Decode the JSON response into a Dart class. return response.data?.map(Package.fromJson).toList() ?? const []; }
  • 40. Immutable State 데이터 일관성 유지 상태가 예측 가능한 방식으로만 변경될 수 있습니다. 버그 감소 상태 변경으로 인한 부작용 (side-effect)을 줄일 수 있습니다. 개발 효율성 향상 상태 변경을 추적하기 쉬워 디버깅이 용이합니다.
  • 41. Clear Separation of Concerns 가독성 향상 코드를 더 이해하기 쉬워지고, 관리가 용이합니다. 유지보수성 향상 각 모듈이 독립적으로 작동하므로, 한 부분의 변경이 다른 부분에 미치는 영향을 최소화합니다. 재사용성 향상 독립적으로 작동하는 모듈은 다른 프로젝트에서도 재사용이 가능합니다.
  • 42. 상태 관리를 통해 UI 로직과 비즈니스 로직을 분리할 수 있습니다. Clear Separation of Concerns
  • 43. dog_images_bloc.dart ... @injectable class DogImagesBloc extends Bloc<DogImagesEvent, DogImagesState> { DogImagesBloc(GetDogImagesUseCase getDogImagesUseCase, ...) : _getDogImagesUseCase = getDogImagesUseCase, ... super(const DogImagesState()) { on<GetRemoteDogImagesEvent>(_onGetRemoteDogImages); } ... void _onGetRemoteDogImages(GetRemoteDogImagesEvent event, Emitter<DogImagesState> emit) async { emit(const RemoteDogImagesLoading()); await _getDogImagesUseCase.call( params: GetDogDto(isRemote: true, limit: event.limit), onSuccess: (data) async => emit(RemoteDogImagesLoaded(data)), onFailure: (error) async => emit(RemoteDogImagesError(error)), ); } }
  • 44. dog_images_event.dart sealed class DogImagesEvent extends Equatable { const DogImagesEvent(); @override List<Object> get props => []; } class GetRemoteDogImagesEvent extends DogImagesEvent { final bool isForceUpdate; final int limit; const GetRemoteDogImagesEvent({this.isForceUpdate = true, this.limit = 10}); } class GetLocalDogImagesEvent extends DogImagesEvent { const GetLocalDogImagesEvent(); } ...
  • 45. dog_images_state.dart sealed class DogImagesState extends Equatable { final List<DogImage> remoteImages; final List<DogImage> localImages; final AppError error; const DogImagesState({ this.remoteImages = const [], this.localImages = const [], this.error = const AppError(), }); @override List<Object> get props => [remoteImages, localImages, error]; } ... class RemoteDogImagesLoading extends DogImagesState { const RemoteDogImagesLoading(); } ...
  • 46. local_dog_card_page.dart ... Widget build(BuildContext context) => BlocBuilder<DogImagesBloc, DogImagesState>( builder: (context, state) { if (state is LocalDogImagesLoading) { return const LoadingDogCardFrame(); } else if (state is LocalDogImagesLoaded) { final cards = state.localImages.map(DogImageCard.new).toList(); return LocalDogCardFrame( items: state.localImages, controller: controller, onSwipe: _onSwipe, cards: cards, onClearAll: () { context.read<DogImagesBloc>().add(const ClearDogImagesEvent()); }, onDelete: () { ...
  • 47. dog_images_bloc.dart ... @injectable class DogImagesBloc extends Bloc<DogImagesEvent, DogImagesState> { DogImagesBloc(GetDogImagesUseCase getDogImagesUseCase, ...) : _getDogImagesUseCase = getDogImagesUseCase, ... super(const DogImagesState()) { on<GetRemoteDogImagesEvent>(_onGetRemoteDogImages); } ... void _onGetRemoteDogImages(GetRemoteDogImagesEvent event, Emitter<DogImagesState> emit) async { emit(const RemoteDogImagesLoading()); await _getDogImagesUseCase.call( params: GetDogDto(isRemote: true, limit: event.limit), onSuccess: (data) async => emit(RemoteDogImagesLoaded(data)), onFailure: (error) async => emit(RemoteDogImagesError(error)), ); } }
  • 48. dog_images_bloc.dart ... @injectable class DogImagesBloc extends Bloc<DogImagesEvent, DogImagesState> { DogImagesBloc(GetDogImagesUseCase getDogImagesUseCase, ...) : _getDogImagesUseCase = getDogImagesUseCase, ... super(const DogImagesState()) { on<GetRemoteDogImagesEvent>(_onGetRemoteDogImages); } ... void _onGetRemoteDogImages(GetRemoteDogImagesEvent event, Emitter<DogImagesState> emit) async { emit(const RemoteDogImagesLoading()); await _getDogImagesUseCase.call( params: GetDogDto(isRemote: true, limit: event.limit), onSuccess: (data) async => emit(RemoteDogImagesLoaded(data)), onFailure: (error) async => emit(RemoteDogImagesError(error)), ); } }
  • 49. local_dog_card_page.dart ... Widget build(BuildContext context) => BlocBuilder<DogImagesBloc, DogImagesState>( builder: (context, state) { if (state is LocalDogImagesLoading) { return const LoadingDogCardFrame(); } else if (state is LocalDogImagesLoaded) { final cards = state.localImages.map(DogImageCard.new).toList(); return LocalDogCardFrame( items: state.localImages, controller: controller, onSwipe: _onSwipe, cards: cards, onClearAll: () { context.read<DogImagesBloc>().add(const ClearDogImagesEvent()); }, onDelete: () { ...
  • 50. local_dog_card_page.dart return Container( clipBehavior: Clip.hardEdge, decoration: BoxDecoration( ... color: Colors.white, boxShadow: [ ... Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( item.breeds.isNotEmpty ? item.breeds[0].name : 'No Name', style: const TextStyle(color: Colors.black, fontWeight: FontWeight.bold, fontSize: 20)), const SizedBox(height: 5), ...
  • 51. Flutter Flutter 에 Clean Architecture 를 얹어보자 - 2/e