在 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();
  }
}