
state의 value가 변할 때 UI를 변경시키는 것이 아닌 dialog, snackbar, bottom sheet, navigation 등과 같은 action을 처리하는 것은 주의가 요구됨.
class AppProvider with ChangeNotifier {
AppState _state = AppState.initial;
AppState get state => _state;
Future<void> getResult(String searchTerm) async {
_state = AppState.loading;
notifyListeners();
await Future.delayed(Duration(seconds: 1));
try {
if (searchTerm == 'fail') {
throw 'Something went wrong';
}
_state = AppState.success;
notifyListeners();
} catch (e) {
_state = AppState.error;
notifyListeners();
// rethrow;
}
}
}
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'app_provider.dart';
import 'success_page.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return ChangeNotifierProvider<AppProvider>(
create: (_) => AppProvider(),
child: MaterialApp(
title: 'addListener of ChangeNotifier',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final GlobalKey<FormState> formKey = GlobalKey<FormState>();
// form을 핸들링할거기때문에 FormState 타입의 GlobalKey 변수 formKey 선언
AutovalidateMode autovalidateMode = AutovalidateMode.disabled;
// form input validation을 위한 AutovalidateMode 타입의 autovalidateMode 변수 선언, 초기값으로 disabled 할당
String? searchTerm;
void submit() async {
// getresult 호출할건데 getresult가 future void 타입이니까 async -
setState(() {
autovalidateMode = AutovalidateMode.always;
// 일단 한번 form이 submit되고 나서 모든 form input에 대해 항상 validation을 수행한다는 것
});
final form = formKey.currentState;
if (form == null || !form.validate()) return;
form.save();
try {
await context.read<AppProvider>().getResult(searchTerm!);
// getresult의 결과가 성공이면 success 페이지로 이동
Navigator.push(context, MaterialPageRoute(
builder: (context) {
return SuccessPage();
},
));
} catch (e) {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
content: Text('Something went wrong'),
);
},
);
}
}
Widget build(BuildContext context) {
final appState = context.watch<AppProvider>().state;
return Scaffold(
body: Center(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 30.0),
child: Form(
key: formKey,
autovalidateMode: autovalidateMode,
child: ListView(
shrinkWrap: true,
children: [
TextFormField(
autofocus: true,
decoration: InputDecoration(
border: OutlineInputBorder(),
label: Text('Search'),
prefixIcon: Icon(Icons.search),
),
validator: (String? value) {
if (value == null || value.trim().isEmpty) {
return 'Search term required';
}
return null;
},
onSaved: (String? value) {
searchTerm = value;
},
),
const SizedBox(height: 20.0),
ElevatedButton(
onPressed: appState == AppState.loading ? null : submit,
child: Text(
appState == AppState.loading ? 'Loading' : 'Get Result',
style: TextStyle(fontSize: 24.0),
),
),
],
),
),
),
),
);
}
}
코드 실행시 fail값을 입력해도 success 화면으로 넘어가는데
이는 provider의 try catch 중 catch처리 부분을 보면 state만 바꿨을 뿐
에러를 throw하지 않았기에 submit함수에서 에러를 캐치하지 못하고 있는 것.
때문에 rethrow를 추가하면 정상 작동함.
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
import 'success_page.dart';
enum AppState {
initial,
loading,
success,
error,
}
class AppProvider with ChangeNotifier {
AppState _state = AppState.initial;
AppState get state => _state;
Future<void> getResult(String searchTerm) async {
_state = AppState.loading;
notifyListeners();
await Future.delayed(Duration(seconds: 1));
try {
if (searchTerm == 'fail') {
throw 'Something went wrong';
}
_state = AppState.success;
notifyListeners();
} catch (e) {
_state = AppState.error;
notifyListeners();
}
}
}
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'app_provider.dart';
import 'success_page.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return ChangeNotifierProvider<AppProvider>(
create: (_) => AppProvider(),
child: MaterialApp(
title: 'addListener of ChangeNotifier',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final GlobalKey<FormState> formKey = GlobalKey<FormState>();
AutovalidateMode autovalidateMode = AutovalidateMode.disabled;
String? searchTerm;
void submit() {
setState(() {
autovalidateMode = AutovalidateMode.always;
});
final form = formKey.currentState;
if (form == null || !form.validate()) return;
form.save();
context.read<AppProvider>().getResult(searchTerm!);
}
Widget build(BuildContext context) {
final appState = context.watch<AppProvider>().state;
if(appState == AppState.success){
WidgetsBinding.instance!.addPostFrameCallback((_) {
Navigator.push(context, MaterialPageRoute(
builder: (context) {
return SuccessPage();
},
));
});
} else if(appState == AppState.error){
WidgetsBinding.instance!.addPostFrameCallback((_) {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
content: Text('Something went wrong'),
);
},
);
});
}
return GestureDetector(
onTap: () => FocusScope.of(context).unfocus(),
child: Scaffold(
body: Center(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 30.0),
child: Form(
key: formKey,
autovalidateMode: autovalidateMode,
child: ListView(
shrinkWrap: true,
children: [
TextFormField(
autofocus: true,
decoration: InputDecoration(
border: OutlineInputBorder(),
label: Text('Search'),
prefixIcon: Icon(Icons.search),
),
validator: (String? value) {
if (value == null || value.trim().isEmpty) {
return 'Search term required';
}
return null;
},
onSaved: (String? value) {
searchTerm = value;
},
),
const SizedBox(height: 20.0),
ElevatedButton(
onPressed: appState == AppState.loading ? null : submit,
child: Text(
appState == AppState.loading ? 'Loading' : 'Get Result',
style: TextStyle(fontSize: 24.0),
),
),
],
),
),
),
),
),
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
import 'success_page.dart';
enum AppState {
initial,
loading,
success,
error,
}
class AppProvider with ChangeNotifier {
AppState _state = AppState.initial;
AppState get state => _state;
Future<void> getResult(BuildContext context, String searchTerm) async {
_state = AppState.loading;
notifyListeners();
await Future.delayed(Duration(seconds: 1));
try {
if (searchTerm == 'fail') {
throw 'Something went wrong';
}
_state = AppState.success;
notifyListeners();
Navigator.push(context, MaterialPageRoute(
builder: (context) {
return SuccessPage();
},
));
} catch (e) {
_state = AppState.error;
notifyListeners();
showDialog(
context: context,
builder: (context) {
return AlertDialog(
content: Text('Something went wrong'),
);
},
);
}
}
}
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'app_provider.dart';
import 'success_page.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return ChangeNotifierProvider<AppProvider>(
create: (_) => AppProvider(),
child: MaterialApp(
title: 'addListener of ChangeNotifier',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final GlobalKey<FormState> formKey = GlobalKey<FormState>();
AutovalidateMode autovalidateMode = AutovalidateMode.disabled;
String? searchTerm;
void submit() {
setState(() {
autovalidateMode = AutovalidateMode.always;
});
final form = formKey.currentState;
if (form == null || !form.validate()) return;
form.save();
context.read<AppProvider>().getResult(context, searchTerm!);
}
Widget build(BuildContext context) {
final appState = context.watch<AppProvider>().state;
return GestureDetector(
onTap: () => FocusScope.of(context).unfocus(),
child: Scaffold(
body: Center(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 30.0),
child: Form(
key: formKey,
autovalidateMode: autovalidateMode,
child: ListView(
shrinkWrap: true,
children: [
TextFormField(
autofocus: true,
decoration: InputDecoration(
border: OutlineInputBorder(),
label: Text('Search'),
prefixIcon: Icon(Icons.search),
),
validator: (String? value) {
if (value == null || value.trim().isEmpty) {
return 'Search term required';
}
return null;
},
onSaved: (String? value) {
searchTerm = value;
},
),
const SizedBox(height: 20.0),
ElevatedButton(
onPressed: appState == AppState.loading ? null : submit,
child: Text(
appState == AppState.loading ? 'Loading' : 'Get Result',
style: TextStyle(fontSize: 24.0),
),
),
],
),
),
),
),
),
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
import 'success_page.dart';
enum AppState {
initial,
loading,
success,
error,
}
class AppProvider with ChangeNotifier {
AppState _state = AppState.initial;
AppState get state => _state;
Future<void> getResult( String searchTerm) async {
_state = AppState.loading;
notifyListeners();
await Future.delayed(Duration(seconds: 1));
try {
if (searchTerm == 'fail') {
throw 'Something went wrong';
}
_state = AppState.success;
notifyListeners();
} catch (e) {
_state = AppState.error;
notifyListeners();
}
}
}
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'app_provider.dart';
import 'success_page.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return ChangeNotifierProvider<AppProvider>(
create: (_) => AppProvider(),
child: MaterialApp(
title: 'addListener of ChangeNotifier',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final GlobalKey<FormState> formKey = GlobalKey<FormState>();
AutovalidateMode autovalidateMode = AutovalidateMode.disabled;
String? searchTerm;
late final AppProvider appProv;
void initState() {
super.initState();
appProv = context.read<AppProvider>();
appProv.addListener(appListener);
}
void appListener() {
// appState의 상태를 체크해야하기 때문에 appProvider의 instance 필요
if (appProv.state == AppState.success) {
Navigator.push(context, MaterialPageRoute(
builder: (context) {
return SuccessPage();
},
));
} else if (appProv.state == AppState.error) {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
content: Text('Something went wrong'),
);
},
);
}
}
void dispose() {
appProv.removeListener(appListener);
super.dispose();
}
void submit() {
setState(() {
autovalidateMode = AutovalidateMode.always;
});
final form = formKey.currentState;
if (form == null || !form.validate()) return;
form.save();
context.read<AppProvider>().getResult(searchTerm!);
}
Widget build(BuildContext context) {
final appState = context.watch<AppProvider>().state;
return GestureDetector(
onTap: () => FocusScope.of(context).unfocus(),
child: Scaffold(
body: Center(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 30.0),
child: Form(
key: formKey,
autovalidateMode: autovalidateMode,
child: ListView(
shrinkWrap: true,
children: [
TextFormField(
autofocus: true,
decoration: InputDecoration(
border: OutlineInputBorder(),
label: Text('Search'),
prefixIcon: Icon(Icons.search),
),
validator: (String? value) {
if (value == null || value.trim().isEmpty) {
return 'Search term required';
}
return null;
},
onSaved: (String? value) {
searchTerm = value;
},
),
const SizedBox(height: 20.0),
ElevatedButton(
onPressed: appState == AppState.loading ? null : submit,
child: Text(
appState == AppState.loading ? 'Loading' : 'Get Result',
style: TextStyle(fontSize: 24.0),
),
),
],
),
),
),
),
),
);
}
}