Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Android physical back-button processing method #88

Closed
coder-xb opened this issue Oct 29, 2020 · 18 comments
Closed

Android physical back-button processing method #88

coder-xb opened this issue Oct 29, 2020 · 18 comments

Comments

@coder-xb
Copy link

How to prevent the Route change of the project page when using BackButtonBehavior.ignore and BackButtonBehavior.close?
The current situation is: when I use BotToast.showAnimationWidget on a page and set the backButtonBehavior property to BackButtonBehavior.ignore or BackButtonBehavior.close, Toast is indeed closed, but the page Route also goes back one page.

@MMMzq
Copy link
Owner

MMMzq commented Oct 30, 2020

Version (please complete the following information):

  • Flutter Version: [e.g. v1.5.0]
  • OS: [e.g. iOS/Android]
  • BotToast Version : [e.g. 0.3.0]

@coder-xb
Copy link
Author

Flutter Version: v1.22.1
OS: Android [v9.0]
BotToast Version: v3.0.4
In addition, there are two [Navigator] in my project, I don’t know if it caused this problem, I am also looking for the problem.

@MMMzq
Copy link
Owner

MMMzq commented Oct 30, 2020

我刚刚试了1.22.1运行example,功能正常.
确实有可能和多个Navigator有关,你能大概描述一下整个结构吗(要包括BotToastInit,MaterialApp,Navigator)?

@coder-xb
Copy link
Author

coder-xb commented Oct 30, 2020

This is my app.dart:

import 'package:flutter/material.dart';
import '../router.dart';
import 'package:provider/provider.dart';
import 'package:bot_toast/bot_toast.dart';
class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State< MyApp > with WidgetsBindingObserver {
  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    super.didChangeAppLifecycleState(state);
    if (state == AppLifecycleState.resumed) {
      // ...检版本更新处理;
    }
  }
  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        ...
      ],
      child: MaterialApp(
        initialRoute: '/',
        onGenerateRoute: AppRouter.generate,
        navigatorObservers: [AppRouter(), BotToastNavigatorObserver()],
        debugShowCheckedModeBanner: false,
        builder: (BuildContext context, Widget child) {
          child = MediaQuery(
              child: child,
              data: MediaQuery.of(context).copyWith(textScaleFactor: 1.0));
          child = (BotToastInit())(context, child);
          return child;
        },
      ),
    );
  }
}

==========================================

And this is my router.dart:

class AppRouter extends NavigatorObserver {
  static AppRouter _router = AppRouter._();
  static final Map<String, Function> _configs = {
    '/': (BuildContext context, [Object args]) => HomePage(),
    '/second': (BuildContext context, [Object args]) => SecondPage(),
  };

  static final Function generate = (RouteSettings settings) {
    final Function builder = _configs[settings.name];
    return CupertinoPageRoute(
      settings: settings,
      builder: (BuildContext context) => settings.arguments == null
          ? builder(context)
          : builder(context, settings.arguments),
    );
  };

  AppRouter._() {
    _stream = StreamController.broadcast();
  }

  factory AppRouter() => _router;

  // 当前路由栈
  static List<Route> _routes = [];
  List<Route> get routes => _routes;
  Route get currentRoute => _routes[_routes.length - 1];
  String get currentName => currentRoute.settings.name;
  // stream相关
  static StreamController _stream;
  StreamController get stream => _stream;

  Future<Object> go(String name, [Object args]) =>
      navigator.pushNamed(name, arguments: args);

  void pop<T extends Object>([T res]) => navigator.pop(res);

  Future<Object> root(String name, [String keep]) =>
      navigator.pushNamedAndRemoveUntil(
          name, keep != null ? ModalRoute.withName(keep) : (r) => r == null);

  Future<Object> replace(String name, [Object args]) =>
      navigator.pushReplacementNamed(name, arguments: args);

  void popUntil(String name) =>
      navigator.popUntil((route) => route.settings.name == name);

  /// 当调用[Navigator.push]
  @override
  void didPush(Route route, Route previousRoute) {
    super.didPush(route, previousRoute);
    // 这里过滤push的是dialog的情况
    if (route is CupertinoPageRoute || route is MaterialPageRoute) {
      _routes.add(route);
      routeObserver();
    }
  }

  /// 当调用[Navigator.replace]
  @override
  void didReplace({Route newRoute, Route oldRoute}) {
    super.didReplace(newRoute: newRoute, oldRoute: oldRoute);
    if (newRoute is CupertinoPageRoute || newRoute is MaterialPageRoute) {
      _routes.remove(oldRoute);
      _routes.add(newRoute);
      routeObserver();
    }
  }

  /// 当调用[Navigator.pop]
  @override
  void didPop(Route route, Route previousRoute) {
    super.didPop(route, previousRoute);
    if (route is CupertinoPageRoute || route is MaterialPageRoute) {
      _routes.remove(route);
      routeObserver();
    }
  }

  @override
  void didRemove(Route removedRoute, Route oldRoute) {
    super.didRemove(removedRoute, oldRoute);
    if (removedRoute is CupertinoPageRoute ||
        removedRoute is MaterialPageRoute) {
      _routes.remove(removedRoute);
      routeObserver();
    }
  }

  void routeObserver() {
    print('路由栈: $routes');
    print('当前路由: $currentRoute');
    print('当前路由名称: $currentName');
    _stream.sink.add(routes);
  }

  void clear() {
    _stream?.close();
    routes?.clear();
  }
}

===========================
The function is normal when I run the example, but that problem exists in my project.Really strange!

@MMMzq
Copy link
Owner

MMMzq commented Oct 30, 2020

你尝试使用下面的初始方式

//BotToastInit方法一定不要放在闭包里调用,否则会影响`WidgetsBinding.instance.addObserver`调用的时机
final botToastBuilder = BotToastInit();  
MaterialApp(
      title: 'BotToast Demo',
      builder: (context, child) {
        child = myBuilder(context,child);  //do something
        child = botToastBuilder(context,child); 
        return child;
      }, 
      navigatorObservers: [BotToastNavigatorObserver()], //2. registered route observer
      home: XxxxPage(),
  )

@coder-xb
Copy link
Author

coder-xb commented Oct 30, 2020

我换了你说的初始方式,问题解决了,这是为什么呢?

final botToastBuilder = BotToastInit();  
MaterialApp(
      title: 'BotToast Demo',
      builder: (context, child) {
        child = myBuilder(context,child);  //do something
        child = botToastBuilder(context,child); 
        // child = (BotToastInit())(context, child); 这俩种方式有什么区别?
        return child;
      }, 
      navigatorObservers: [BotToastNavigatorObserver()], //2. registered route observer
  )

@coder-xb
Copy link
Author

另外我有一个建议:我现在用showAnimationWiget模拟了flutter原生的showBottomSheet,但是在这个Widget里面我有一个选择地址的按钮,这个按钮需要跳转到其他页面,也就是说我希望的效果不是crossPage,而是在当前界面show一个Toast的时候不是crossPage而是Keep(保持)在当前页面,当我push另外页面之后回来的时候toast保持显示,我这样说能明白我说的这个效果吗?

@coder-xb
Copy link
Author

就像这样:
QQ20201030-114454@2x

@MMMzq
Copy link
Owner

MMMzq commented Oct 30, 2020

这是因为BackButtonBehavior功能依赖 WidgetsBinding.instance.addObserver(this);,而底层didPopRoute的回调顺序是按照注册顺序来调用的,所以如果放在闭包里调用会导致注册顺序在MaterialApp注册之后再去注册

@MMMzq
Copy link
Owner

MMMzq commented Oct 30, 2020

你提的这个需求最好是用Navigator.push的方式实现,不太建议使用BotToast来实现(另外一点:如果显示的toast的包含TextFieldwidget会有别的问题出现请看#71)

@coder-xb
Copy link
Author

这是因为BackButtonBehavior功能依赖 WidgetsBinding.instance.addObserver(this);,而底层didPopRoute的回调顺序是按照注册顺序来调用的,所以如果放在闭包里调用会导致注册顺序在MaterialApp注册之后再去注册

好的,我知道了,谢谢大佬

@coder-xb
Copy link
Author

现在BotToast的组件添加顺序是怎么样的?我现在遇到一个问题:在showAnimationWidget之后如果再showLoading,偶尔会出现showLoading会被showAnimationWidget遮挡住

@MMMzq
Copy link
Owner

MMMzq commented Oct 30, 2020

同一组的toast会按调用顺序显示,不同组的toast不能确定其顺序,这个显示顺序的优化会后面版本优化 :)

@MMMzq MMMzq added the enhancement New feature or request label Oct 30, 2020
@coder-xb
Copy link
Author

我这里貌似初步实现了显示顺序的优化,你看一下有没有什么问题:

import 'package:bot_toast/src/toast_widget/toast_widget.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';

void safeRun(void Function() callback) {
  SchedulerBinding.instance.addPostFrameCallback((_) {
    callback();
  });
  SchedulerBinding.instance.ensureVisualUpdate();
}

class BotToastManager extends StatefulWidget {
  final Widget child;

  const BotToastManager({Key key, this.child}) : super(key: key);

  @override
  BotToastManagerState createState() => BotToastManagerState();
}

class BotToastManagerState extends State<BotToastManager> {
  // final Map<String, Map<UniqueKey, Widget>> _map = {};
  final List<BotToastWidget> _toasts = [];
  final Set<UniqueKey> _pending = Set<UniqueKey>();

  /*List<Widget> get _children => _map.values.fold([], (value, items) {
    return value..addAll(items.values);
  });*/
  List<Widget> get _children => _toasts.fold([], (v, e) => v..add(e.widget));

  void insert(String groupKey, UniqueKey key, Widget widget) {
    safeRun(() {
      // _map[groupKey] ??= {};
      final uniqueKey = UniqueKey();

      widget = ProxyInitState(
        initStateCallback: () {
          _pending.remove(key);
        },
        child: widget,
      );

      widget = ProxyDispose(
        key: uniqueKey,
        child: widget,
        disposeCallback: () {
          // _map[groupKey]?.remove(key);
          _toasts.removeWhere((e) => e.groupKey == groupKey && e.key == key);
        },
      );
      // _map[groupKey][key] = widget;
      _toasts.add(BotToastWidget(groupKey, key, widget));
      _pending.add(key);
      _update();
    });
  }

  void remove(String groupKey, UniqueKey key) {
    safeRun(() {
      if (_pending.contains(key)) {
        // 首桢渲染完成之前,就被删除,需要确保ProxyDispose被安装,因此要放到下一帧进行删除
        return remove(groupKey, key);
      } else {
        // _map[groupKey]?.remove(key);
        _toasts.removeWhere((e) => e.groupKey == groupKey && e.key == key);
        _update();
      }
    });
  }

  void removeAll(String groupKey) {
    safeRun(() {
      /*if (_map[groupKey] == null) {
        return;
      }*/
      if (!_toasts.any((e) => e.groupKey == groupKey)) return;

      // _map[groupKey].removeWhere((key, _) => !_pending.contains(key));
      _toasts.removeWhere((e) => !_pending.contains(e.key));
      _update();
      /*if (_map[groupKey].isNotEmpty) {
        _map[groupKey].forEach((key, value) {
          return remove(groupKey, key);
        });
      }*/
      _toasts.forEach((e) {
        if (e.groupKey == groupKey) remove(groupKey, e.key);
      });
    });
  }

  void cleanAll() {
    safeRun(() {
      _toasts.removeWhere((e) => !_pending.contains(e.key));
      _toasts.forEach((e) => remove(e.groupKey, e.key));
      /*_map.forEach((groupKey, value) {
        assert(value != null);
        value.removeWhere((key, _) => !_pending.contains(key));

        if (value.isNotEmpty) {
          value.forEach((key, value) {
            return remove(groupKey, key);
          });
        }
      });*/
      _update();
    });
  }

  void _update() {
    if (mounted) {
      setState(() {});
    }
  }

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: <Widget>[
        widget.child,
      ]..addAll(_children),
    );
  }
}

class BotToastWidget {
  final String groupKey;
  final UniqueKey key;
  final Widget widget;

  BotToastWidget(this.groupKey, this.key, this.widget)
      : assert(key != null),
        assert(groupKey != null),
        assert(widget != null);
}

@MMMzq
Copy link
Owner

MMMzq commented Oct 30, 2020

我已经改好了,推在dev分支上,谢谢了 :)

@coder-xb
Copy link
Author

coder-xb commented Oct 30, 2020

我已经改好了,推在dev分支上,谢谢了 :)

没事,只是我初步实现并运用到了我的项目中,具体有没有问题,还要多测试呢...
我看了你的在dev分支上的代码,你的实现方式比我的实现方式更容易让人理解 :)

@MMMzq
Copy link
Owner

MMMzq commented Nov 4, 2020

v3.0.5 released

@no-response
Copy link

no-response bot commented Nov 12, 2020

Without additional information, we are unfortunately not sure how to resolve this issue. We are therefore reluctantly going to close this bug for now. Please don't hesitate to comment on the bug if you have any more information for us; we will reopen it right away!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants