在 Flutter/Dart 中使用 Either 显示对话框而不是 return 小部件
Show a dialog instead of return a widget using Either in Flutter/Dart
我有点卡住了,无法弄清楚架构流程在这个用例中应该如何工作。我对函数式编程几乎一无所知,我正在使用函数式编程包 dartz 包中的这个 Either。有人可以帮我解决以下问题:
如果出现错误,我想显示一个弹出对话框而不是小部件。但是 Either 的设计似乎不允许这样做,因为如果逻辑当然需要一个小部件的话。有没有更好的设计可以实现?
学习错误处理here
import 'dart:convert';
import 'dart:io';
import 'package:dartz/dartz.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:awesome_dialog/awesome_dialog.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return ProviderScope(
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.orange,
),
home: const HomePage(),
),
);
}
}
class HomePage extends StatelessWidget {
const HomePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.blueGrey,
body: Flex(
direction: Axis.horizontal,
children: [
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Consumer(
builder: (ctx, ref, child) {
if (ref.watch(notifier).state == NotifierState.initial) {
return const Text('Press the button');
} else if (ref.watch(notifier).state == NotifierState.loading) {
return const CircularProgressIndicator();
} else {
return ref.read(notifier).post.fold(
(failure) {
showDialog( /// Error here, expects a widget
context: context,
barrierDismissible: true,
builder: (BuildContext context) => AlertDialog(
title: Text(failure.toString()),
),
);
},
(post) => Text(post.toString()),
);
}
},
),
Consumer(
builder: (ctx, ref, child) {
return ElevatedButton(
onPressed: () {
ref.read(notifier).getOnePost();
},
child: const Text('Get Post'));
},
),
],
),
),
],
),
);
}
}
class PostService {
final httpClient = FakeHttpClient();
Future<Post?> getOnePost() async {
try {
final responseBody = await httpClient.getResponseBody();
return Post.fromJson(responseBody);
} on SocketException {
throw Failure('No Internet connection ');
} on HttpException {
throw Failure("Couldn't find the post ");
} on FormatException {
throw Failure("Bad response format ");
}
}
}
class FakeHttpClient {
Future<String> getResponseBody() async {
await Future.delayed(const Duration(milliseconds: 500));
//! No Internet Connection
// throw SocketException('No Internet');
//! 404
throw HttpException('404');
//! Invalid JSON (throws FormatException)
// return 'abcd';
// return '{"userId":1,"id":1,"title":"nice title","body":"cool body"}';
}
}
enum NotifierState { initial, loading, loaded }
final notifier = ChangeNotifierProvider((ref) => PostChangeNotifier());
class PostChangeNotifier extends ChangeNotifier {
final _postService = PostService();
NotifierState _state = NotifierState.initial;
NotifierState get state => _state;
void _setState(NotifierState state) {
_state = state;
notifyListeners();
}
late Either<Failure, Post?> _post;
Either<Failure, Post?> get post => _post;
// Set post
void _setPost(Either<Failure, Post?> post) {
_post = post;
notifyListeners();
}
// Set one post
void getOnePost() async {
_setState(NotifierState.loading);
await Task(() => _postService.getOnePost())
.attempt()
.mapLeftToFailure()
.run()
.then((value) => _setPost(value as Either<Failure, Post?>));
_setState(NotifierState.loaded);
}
}
extension TaskX<T extends Either<Object, U>, U> on Task<T> {
Task<Either<Failure, U>> mapLeftToFailure() {
return map(
(either) => either.leftMap((obj) {
try {
return obj as Failure;
} catch (e) {
throw obj;
}
}),
);
}
}
class Post {
final int id;
final int userId;
final String title;
final String body;
Post({
required this.id,
required this.userId,
required this.title,
required this.body,
});
static Post? fromMap(Map<String, dynamic> map) {
return Post(
id: map['id'],
userId: map['userId'],
title: map['title'],
body: map['body'],
);
}
static Post? fromJson(String source) => fromMap(json.decode(source));
@override
String toString() {
return 'Post id: $id, userId: $userId, title: $title, body: $body';
}
}
class Failure {
// Use something like "int code;" if you want to translate error messages
final String message;
Failure(this.message);
@override
String toString() => message;
}
你不调用函数而不是小部件,你应该调用 class 并在 initState
中初始化你的对话框
// call show dialog
(failure) {
ShowDialogScreen(failure: failure.toString());
},
// show dialog screen
class ShowDialogScreen extends StatefulWidget {
final String failure;
const ShowDialogScreen({Key key, this.failure}) : super(key: key);
@override
_ShowDialogScreenState createState() => _ShowDialogScreenState();
}
class _ShowDialogScreenState extends State<ShowDialogScreen> {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) async {
await showDialog(
context: context,
barrierDismissible: true,
builder: (BuildContext context) => AlertDialog(
title: Text(widget.failure),
),
);
});
}
@override
Widget build(BuildContext context) {
return Container();
}
}
我有点卡住了,无法弄清楚架构流程在这个用例中应该如何工作。我对函数式编程几乎一无所知,我正在使用函数式编程包 dartz 包中的这个 Either。有人可以帮我解决以下问题:
如果出现错误,我想显示一个弹出对话框而不是小部件。但是 Either 的设计似乎不允许这样做,因为如果逻辑当然需要一个小部件的话。有没有更好的设计可以实现?
学习错误处理here
import 'dart:convert';
import 'dart:io';
import 'package:dartz/dartz.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:awesome_dialog/awesome_dialog.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return ProviderScope(
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.orange,
),
home: const HomePage(),
),
);
}
}
class HomePage extends StatelessWidget {
const HomePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.blueGrey,
body: Flex(
direction: Axis.horizontal,
children: [
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Consumer(
builder: (ctx, ref, child) {
if (ref.watch(notifier).state == NotifierState.initial) {
return const Text('Press the button');
} else if (ref.watch(notifier).state == NotifierState.loading) {
return const CircularProgressIndicator();
} else {
return ref.read(notifier).post.fold(
(failure) {
showDialog( /// Error here, expects a widget
context: context,
barrierDismissible: true,
builder: (BuildContext context) => AlertDialog(
title: Text(failure.toString()),
),
);
},
(post) => Text(post.toString()),
);
}
},
),
Consumer(
builder: (ctx, ref, child) {
return ElevatedButton(
onPressed: () {
ref.read(notifier).getOnePost();
},
child: const Text('Get Post'));
},
),
],
),
),
],
),
);
}
}
class PostService {
final httpClient = FakeHttpClient();
Future<Post?> getOnePost() async {
try {
final responseBody = await httpClient.getResponseBody();
return Post.fromJson(responseBody);
} on SocketException {
throw Failure('No Internet connection ');
} on HttpException {
throw Failure("Couldn't find the post ");
} on FormatException {
throw Failure("Bad response format ");
}
}
}
class FakeHttpClient {
Future<String> getResponseBody() async {
await Future.delayed(const Duration(milliseconds: 500));
//! No Internet Connection
// throw SocketException('No Internet');
//! 404
throw HttpException('404');
//! Invalid JSON (throws FormatException)
// return 'abcd';
// return '{"userId":1,"id":1,"title":"nice title","body":"cool body"}';
}
}
enum NotifierState { initial, loading, loaded }
final notifier = ChangeNotifierProvider((ref) => PostChangeNotifier());
class PostChangeNotifier extends ChangeNotifier {
final _postService = PostService();
NotifierState _state = NotifierState.initial;
NotifierState get state => _state;
void _setState(NotifierState state) {
_state = state;
notifyListeners();
}
late Either<Failure, Post?> _post;
Either<Failure, Post?> get post => _post;
// Set post
void _setPost(Either<Failure, Post?> post) {
_post = post;
notifyListeners();
}
// Set one post
void getOnePost() async {
_setState(NotifierState.loading);
await Task(() => _postService.getOnePost())
.attempt()
.mapLeftToFailure()
.run()
.then((value) => _setPost(value as Either<Failure, Post?>));
_setState(NotifierState.loaded);
}
}
extension TaskX<T extends Either<Object, U>, U> on Task<T> {
Task<Either<Failure, U>> mapLeftToFailure() {
return map(
(either) => either.leftMap((obj) {
try {
return obj as Failure;
} catch (e) {
throw obj;
}
}),
);
}
}
class Post {
final int id;
final int userId;
final String title;
final String body;
Post({
required this.id,
required this.userId,
required this.title,
required this.body,
});
static Post? fromMap(Map<String, dynamic> map) {
return Post(
id: map['id'],
userId: map['userId'],
title: map['title'],
body: map['body'],
);
}
static Post? fromJson(String source) => fromMap(json.decode(source));
@override
String toString() {
return 'Post id: $id, userId: $userId, title: $title, body: $body';
}
}
class Failure {
// Use something like "int code;" if you want to translate error messages
final String message;
Failure(this.message);
@override
String toString() => message;
}
你不调用函数而不是小部件,你应该调用 class 并在 initState
// call show dialog
(failure) {
ShowDialogScreen(failure: failure.toString());
},
// show dialog screen
class ShowDialogScreen extends StatefulWidget {
final String failure;
const ShowDialogScreen({Key key, this.failure}) : super(key: key);
@override
_ShowDialogScreenState createState() => _ShowDialogScreenState();
}
class _ShowDialogScreenState extends State<ShowDialogScreen> {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) async {
await showDialog(
context: context,
barrierDismissible: true,
builder: (BuildContext context) => AlertDialog(
title: Text(widget.failure),
),
);
});
}
@override
Widget build(BuildContext context) {
return Container();
}
}