Flutter导航器2.0页面使用showGeneralDialog处理页面清除

3
我正在尝试使用Navigator 2.0 pagesshowGeneralDialog开发注销功能。对话框(由showGeneralDialog创建)将在用户单击对话框中的按钮并关闭对话框后处理注销。但是,在_RouteEntry.markForComplete中抛出了一个错误(该错误由assert语句抛出)。
我尝试使用一些简单代码创建了一个虚拟项目:
import 'package:flutter/material.dart';

void main() {
  runApp(BooksApp());
}

class Book {
  final String title;
  final String author;

  Book(this.title, this.author);
}

class BooksApp extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => _BooksAppState();
}

class _BooksAppState extends State<BooksApp> {
  BookRouterDelegate _routerDelegate = BookRouterDelegate();
  BookRouteInformationParser _routeInformationParser =
      BookRouteInformationParser();
  PlatformRouteInformationProvider _platformRouteInformationProvider =
      PlatformRouteInformationProvider(
          initialRouteInformation: RouteInformation(location: '/'));

  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      title: 'Books App',
      routerDelegate: _routerDelegate,
      routeInformationParser: _routeInformationParser,
      routeInformationProvider: _platformRouteInformationProvider,
    );
  }
}

class BookRouteInformationParser extends RouteInformationParser<RoutePath> {
  @override
  Future<RoutePath> parseRouteInformation(
      RouteInformation routeInformation) async {
    final uri = Uri.parse(routeInformation.location);

    if (uri.pathSegments.length >= 2) {
      var remaining = uri.pathSegments[1];
      return RoutePath.details(int.tryParse(remaining));
    } else if (uri.pathSegments.length > 0 && uri.pathSegments[0] == 'book') {
      return RoutePath.home();
    } else
      return RoutePath.login();
  }

  @override
  RouteInformation restoreRouteInformation(RoutePath path) {
    if (path.isLogin) return RouteInformation(location: '/');
    if (path.isHomePage) {
      return RouteInformation(location: '/book');
    }
    if (path.isDetailsPage) {
      return RouteInformation(location: '/book/${path.id}');
    }
    return null;
  }
}

class BookRouterDelegate extends RouterDelegate<RoutePath>
    with ChangeNotifier, PopNavigatorRouterDelegateMixin<RoutePath> {
  final GlobalKey<NavigatorState> navigatorKey;

  Book _selectedBook;

  List<Book> books = [
    Book('Stranger in a Strange Land', 'Robert A. Heinlein'),
    Book('Foundation', 'Isaac Asimov'),
    Book('Fahrenheit 451', 'Ray Bradbury'),
  ];

  BookRouterDelegate() : navigatorKey = GlobalKey<NavigatorState>();

  bool _showLogin = false;

  RoutePath get currentConfiguration => _showLogin
      ? RoutePath.login()
      : _selectedBook == null
          ? RoutePath.home()
          : RoutePath.details(books.indexOf(_selectedBook));

  @override
  Widget build(BuildContext context) {
    return Navigator(
      key: navigatorKey,
      pages: [
        if (currentConfiguration.isLogin)
          MaterialPage(
            key: ValueKey('LoginPage'),
            child: Scaffold(
              appBar: AppBar(
                title: Text('Login'),
              ),
            ),
          ),
        if (!currentConfiguration.isLogin)
          MaterialPage(
            key: ValueKey('BooksListPage'),
            child: BooksListScreen(
              books: books,
              onTapped: _handleBookTapped,
            ),
          ),
        if (_selectedBook != null) BookDetailsPage(book: _selectedBook)
      ],
      onPopPage: (route, result) {
        if (!route.didPop(result)) {
          return false;
        }

        // Update the list of pages by setting _selectedBook to null
        _selectedBook = null;
        notifyListeners();

        return true;
      },
    );
  }

  @override
  Future<void> setNewRoutePath(RoutePath path) async {
    if (path.isDetailsPage) {
      _selectedBook = books[path.id];
    }
  }

  void _handleBookTapped(Book book) {
    _selectedBook = book;
    notifyListeners();
  }

  void handleLogout() {
    _showLogin = true;
    _selectedBook = null;
    notifyListeners();
  }
}

class BookDetailsPage extends Page {
  final Book book;

  BookDetailsPage({
    this.book,
  }) : super(key: ValueKey(book));

  Route createRoute(BuildContext context) {
    return MaterialPageRoute(
      settings: this,
      builder: (BuildContext context) {
        return BookDetailsScreen(book: book);
      },
    );
  }
}

class RoutePath {
  final bool isLogin;
  final int id;

  RoutePath.login()
      : id = null,
        isLogin = true;

  RoutePath.home()
      : id = null,
        isLogin = false;

  RoutePath.details(this.id) : isLogin = false;

  bool get isHomePage => id == null;

  bool get isDetailsPage => id != null;
}

class BooksListScreen extends StatelessWidget {
  final List<Book> books;
  final ValueChanged<Book> onTapped;

  BooksListScreen({
    @required this.books,
    @required this.onTapped,
  });

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: ListView(
        children: [
          for (var book in books)
            ListTile(
              title: Text(book.title),
              subtitle: Text(book.author),
              onTap: () => onTapped(book),
            )
        ],
      ),
    );
  }
}

class BookDetailsScreen extends StatelessWidget {
  final Book book;

  BookDetailsScreen({
    @required this.book,
  });

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        actions: [
          IconButton(
            icon: Icon(Icons.exit_to_app),
            onPressed: () async {
              final response = await showGeneralDialog<bool>(
                context: context,
                useRootNavigator: true,
                barrierDismissible: false,
                transitionDuration: const Duration(milliseconds: 300),
                transitionBuilder: (context, animation, __, child) {
                  return ScaleTransition(
                    scale: animation,
                    child: child,
                  );
                },
                pageBuilder: (context, _, __) => _CustomDialog(),
              );

              if (response == null) return;
              if (response) {
                // await Future.delayed(const Duration(milliseconds: 300));
                context
                    .findAncestorStateOfType<_BooksAppState>()
                    ._routerDelegate
                    .handleLogout();
              }
            },
          ),
        ],
      ),
      body: Padding(
        padding: const EdgeInsets.all(8.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            if (book != null) ...[
              ListTile(
                title: Text(book.title),
                subtitle: Text(book.author,
                    style: Theme.of(context).textTheme.subtitle1),
              ),
            ],
          ],
        ),
      ),
    );
  }
}

class _CustomDialog extends StatelessWidget {
  _CustomDialog({
    Key key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Dialog(
      child: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            Text('Clear all pages?'),
            RaisedButton(
              child: Text('OK'),
              onPressed: () {
                Navigator.of(context, rootNavigator: true).pop(true);
              },
            ),
          ],
        ),
      ),
    );
  }
}


然而,在这个虚拟项目中,错误是随机抛出的,有时会在NavigatorState.finalizeRoute处抛出(在这个断言语句中:assert(_history.where(_RouteEntry.isRoutePredicate(route)).length == 1);),有时会在与我提到的完全相同的位置抛出,即_RouteEntry.markForComplete
目前我能找到的解决方法是延迟直到对话框弹出过渡完成(在context.findAncestorStateOfType<_BooksAppState>()._routerDelegate.handleLogout();之前延迟)。
然而,我想知道正确的修复方法,而不是等待它完全弹出,因为我不确定是否会遇到任何隐藏的问题。

我已将此问题报告给 https://github.com/flutter/flutter/issues/68162。 - Taboo Sun
1个回答

0

你提交的问题单似乎已经得到解决。无需采取任何变通方法,只需更新Flutter SDK版本即可解决该问题。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接