Flutter: Модификация Scaffold для отслеживания отображаемых в данный момент Scaffolds BuildContexts для методов SnackBar всего приложения - PullRequest
0 голосов
/ 08 февраля 2020

Я пытаюсь отследить текущие Scaffold с (их BuildContext с), чтобы создать функцию SnackBar для всего приложения. В настоящее время я создаю class, который представляет Scaffold и добавляет его context к другому class, который управляет текущими Scaffold с. Однако у меня не получилось, так как моя текущая попытка имеет две проблемы:

  1. Не правильно хранится текущая Scaffold s
  2. Видимо, метод dispose слишком поздний для удаления Scaffold s BuildContext из List текущего Scaffold s 'BuildContext s, так что это дает мне Exception, "Looking up a deactivated widget's ancestor is unsafe."

Вот моя текущая попытка:

  1. Реализация (main.dart):
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

import 'MScaffold.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Snackbar manager',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Snackbar manager'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;

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

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return MScaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(),
      floatingActionButton: Column(
        mainAxisSize: MainAxisSize.min,
        children: <Widget>[
          FloatingActionButton(
            heroTag: 0,
            child:Icon(Icons.add_circle_outline),
            onPressed: (){
              MScaffoldManager.showSnackbar();
            },
          ),
          FloatingActionButton(
            heroTag: 1,
            child:Icon(Icons.remove_circle_outline),
            onPressed: (){
              MScaffoldManager.hideSnackbar();
            },
          ),
          FloatingActionButton(
            heroTag: 2,
            child:Icon(Icons.add),
            onPressed: (){
              Navigator.of(context).push(
                MaterialPageRoute(
                  builder: (context){
                    return SecondScaffold();
                  }
                )
              );
            },
          ),
        ],
      ),
    );
  }
}

class SecondScaffold extends StatelessWidget{
  @override
  Widget build(BuildContext context){
    return MScaffold(
      appBar: AppBar(
        title: Text("Page 2"),
      ),
      body: Center(),
      floatingActionButton: Column(
        mainAxisSize: MainAxisSize.min,
        children: <Widget>[
          FloatingActionButton(
            heroTag: 0,
            child:Icon(Icons.add_circle_outline),
            onPressed: (){
              MScaffoldManager.showSnackbar();
            },
          ),
          FloatingActionButton(
            heroTag: 1,
            child:Icon(Icons.remove_circle_outline),
            onPressed: (){
              MScaffoldManager.hideSnackbar();
            },
          ),
          FloatingActionButton(
            heroTag: 2,
            child:Icon(Icons.remove),
            onPressed: (){
              Navigator.of(context).pop();
            },
          ),
        ],
      ),
    );
  }
}
library class es: MScaffoldManager; MScaffold; и MScaffoldState:

import 'package:flutter/cupertino.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';

class MScaffoldManager{
  static List<Map> scaffoldInformation = List();
  static void addScaffold(context){
    scaffoldInformation.add({'context':context});
    print("Scaffold added:\n"+scaffoldInformation.toString());
  }
  static void removeScaffold(context){
    Scaffold.of(context).hideCurrentSnackBar();
    scaffoldInformation.remove({'context':context});
    print("Scaffold removed:\n"+scaffoldInformation.toString());
  }
  static void showSnackbar(){
    scaffoldInformation.forEach((v){
      Scaffold.of(v['context']).showSnackBar(SnackBar(
        content: Text("Snackbar works"),
      ));
    });
  }
  static void hideSnackbar(){
    scaffoldInformation.forEach((v){
      Scaffold.of(v['context']).hideCurrentSnackBar();
    });
  }
}


class MScaffold extends StatefulWidget{
  Key key;
  var appBar;
  var body;
  var floatingActionButton;
  var floatingActionButtonLocation;
  var floatingActionButtonAnimator;
  var persistentFooterButtons;
  var drawer;
  var endDrawer;
  var bottomNavigationBar;
  var bottomSheet;
  var backgroundColor;
  var resizeToAvoidBottomPadding;
  var resizeToAvoidBottomInset;
  var primary;
  var drawerDragStartBehavior;
  var extendBody;
  var extendBodyBehindAppBar;
  var drawerScrimColor;
  var drawerEdgeDragWidth;

  MScaffold({
    Key key,
    this.appBar,
    this.body,
    this.floatingActionButton,
    this.floatingActionButtonLocation,
    this.floatingActionButtonAnimator,
    this.persistentFooterButtons,
    this.drawer,
    this.endDrawer,
    this.bottomNavigationBar,
    this.bottomSheet,
    this.backgroundColor,
    this.resizeToAvoidBottomPadding,
    this.resizeToAvoidBottomInset,
    this.primary = true,
    this.drawerDragStartBehavior = DragStartBehavior.start,
    this.extendBody = false,
    this.extendBodyBehindAppBar = false,
    this.drawerScrimColor,
    this.drawerEdgeDragWidth,
  })  : assert(primary != null),
      assert(extendBody != null),
      assert(extendBodyBehindAppBar != null),
      assert(drawerDragStartBehavior != null);

  @override
  State<StatefulWidget> createState() {
    return MScaffoldState(
      key: key,
      appBar: appBar,
      body: body,
      floatingActionButton: floatingActionButton,
      floatingActionButtonLocation: floatingActionButtonLocation,
      floatingActionButtonAnimator: floatingActionButtonAnimator,
      persistentFooterButtons: persistentFooterButtons,
      drawer: drawer,
      endDrawer: endDrawer,
      bottomNavigationBar: bottomNavigationBar,
      bottomSheet: bottomSheet,
      backgroundColor: backgroundColor,
      resizeToAvoidBottomPadding: resizeToAvoidBottomPadding,
      resizeToAvoidBottomInset: resizeToAvoidBottomInset,
      primary: primary,
      drawerDragStartBehavior: drawerDragStartBehavior,
      extendBody: extendBody,
      extendBodyBehindAppBar: extendBodyBehindAppBar,
      drawerScrimColor: drawerScrimColor,
      drawerEdgeDragWidth: drawerEdgeDragWidth,
    );
  }

}

class MScaffoldState extends State<MScaffold> {
  Key key;
  var appBar;
  var body;
  var floatingActionButton;
  var floatingActionButtonLocation;
  var floatingActionButtonAnimator;
  var persistentFooterButtons;
  var drawer;
  var endDrawer;
  var bottomNavigationBar;
  var bottomSheet;
  var backgroundColor;
  var resizeToAvoidBottomPadding;
  var resizeToAvoidBottomInset;
  var primary;
  var drawerDragStartBehavior;
  var extendBody;
  var extendBodyBehindAppBar;
  var drawerScrimColor;
  var drawerEdgeDragWidth;

  MScaffoldState({
    Key key,
    this.appBar,
    this.body,
    this.floatingActionButton,
    this.floatingActionButtonLocation,
    this.floatingActionButtonAnimator,
    this.persistentFooterButtons,
    this.drawer,
    this.endDrawer,
    this.bottomNavigationBar,
    this.bottomSheet,
    this.backgroundColor,
    this.resizeToAvoidBottomPadding,
    this.resizeToAvoidBottomInset,
    this.primary = true,
    this.drawerDragStartBehavior = DragStartBehavior.start,
    this.extendBody = false,
    this.extendBodyBehindAppBar = false,
    this.drawerScrimColor,
    this.drawerEdgeDragWidth,
  })  : assert(primary != null),
      assert(extendBody != null),
      assert(extendBodyBehindAppBar != null),
      assert(drawerDragStartBehavior != null);

  @override
  void initState() {
    super.initState();
  }
  @override
  dispose(){
    MScaffoldManager.removeScaffold(_scaffoldContext);
    super.dispose();

  }
  BuildContext _scaffoldContext;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      key: key,
      appBar: appBar,
      body: Builder(
        builder: (context){
          if(_scaffoldContext!=null)
            MScaffoldManager.removeScaffold(_scaffoldContext);
          _scaffoldContext = context;
          MScaffoldManager.addScaffold(_scaffoldContext);
          return body;
        },
      ),
      floatingActionButton: floatingActionButton,
      floatingActionButtonLocation: floatingActionButtonLocation,
      floatingActionButtonAnimator: floatingActionButtonAnimator,
      persistentFooterButtons: persistentFooterButtons,
      drawer: drawer,
      endDrawer: endDrawer,
      bottomNavigationBar: bottomNavigationBar,
      bottomSheet: bottomSheet,
      backgroundColor: backgroundColor,
      resizeToAvoidBottomPadding: resizeToAvoidBottomPadding,
      resizeToAvoidBottomInset: resizeToAvoidBottomInset,
      primary: primary,
      drawerDragStartBehavior: drawerDragStartBehavior,
      extendBody: extendBody,
      extendBodyBehindAppBar: extendBodyBehindAppBar,
      drawerScrimColor: drawerScrimColor,
      drawerEdgeDragWidth: drawerEdgeDragWidth,
    );
  }
}

Признавая, что он не работает как есть, он также кажется немного многословным и немного грязным. Я хотел бы просто сделать Scaffold class, который функционирует так же, как Scaffold, но работает с менеджером class, который отслеживает все Scaffold s 'context s. поэтому я могу легко отображать SnackBar сообщений независимо от того, на какой странице находится пользователь.

Ответы [ 2 ]

1 голос
/ 08 февраля 2020

Я сам не пробовал этот код, но как насчет того, чтобы создать ChangeNotifier для использования с Provider и прикрепить его над вашим MaterialApp:

class MyErrorChangeNotifier extends ChangeNotifier {
  String error;

  setError(String error) {
    this.error = error;

    notifyListeners();
  }
}

И затем создать Пользовательский Scaffold, который будет отображать SnackBar при каждом вызове setError, если смонтирован Scaffold:

class MyScaffold extends Scaffold {
  // TODO constructor

  @override
  ScaffoldState createState() => MyScaffoldState();
}

class MyScaffoldState extends ScaffoldState {
  MyErrorChangeNotifier _myErrorCN;
  Function() _listener;

  @override
  void initState() {
    super.initState();

    _listener = () {
      if (mounted) {
        showSnackBar(SnackBar(content: Text(_myErrorCN.error)));
      }
    };

    Future.microtask(() {
      _myErrorCN = Provider.of<MyErrorChangeNotifier>(context, listen: false)..addListener(_listener);
    });
  }

  @override
  void dispose() {
    _myErrorCN?.removeListener(_listener);

    super.dispose();
  }
}

Всякий раз, когда у вас есть такой вариант использования, попробуйте придумать решение с использованием поставщика - данные отправляются по иерархии контекста в ChangeNotifier, который затем отправляет данные обратно по иерархии контекста всем виджетам, которые их прослушивают.

0 голосов
/ 11 февраля 2020

Благодаря некоторой помощи от Овидиу я нашел хороший ответ. Я решил за go Provider в пользу стати c класса. Я думаю, что реализация проще. Кроме того, я также заставил его показывать SnackBar на новой странице (с новой Scaffold), если она открыта.

Все, что вам нужно сделать для ее реализации, это import MScaffold.dart и измените ваши Scaffold s на MScaffold s.

Я считаю этот метод очень удобным, поэтому он может помочь другим людям, которые также ищут простой способ показать SnackBar независимо от того, какой Scaffold отображается в данный момент.

Вот страница презентации:

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

import 'MScaffold.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Snackbar manager',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Snackbar manager'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;

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

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return MScaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(),
      floatingActionButton: Column(
        mainAxisSize: MainAxisSize.min,
        children: <Widget>[
          FloatingActionButton(
            heroTag: 0,
            child: Icon(Icons.add_circle_outline),
            onPressed: () {
              ShowSnackBar().showText("You were on page 1");
            },
          ),
          FloatingActionButton(
            heroTag: 1,
            child: Icon(Icons.remove_circle_outline),
            onPressed: () {
              ShowSnackBar().hide();
            },
          ),
          FloatingActionButton(
            heroTag: 2,
            child: Icon(Icons.add),
            onPressed: () {
              Navigator.of(context).push(MaterialPageRoute(builder: (context) {
                return SecondScaffold();
              }));
            },
          ),
        ],
      ),
    );
  }
}

class SecondScaffold extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MScaffold(
      appBar: AppBar(
        title: Text("Page 2"),
      ),
      body: Center(),
      floatingActionButton: Column(
        mainAxisSize: MainAxisSize.min,
        children: <Widget>[
          FloatingActionButton(
            heroTag: 0,
            child: Icon(Icons.add_circle_outline),
            onPressed: () {
              ShowSnackBar().show(
                SnackBar(
                  content: Text("You were on page 2"),
                ),
              );
            },
          ),
          FloatingActionButton(
            heroTag: 1,
            child: Icon(Icons.remove_circle_outline),
            onPressed: () {
              ShowSnackBar().hide();
            },
          ),
          FloatingActionButton(
            heroTag: 2,
            child: Icon(Icons.remove),
            onPressed: () {
              Navigator.of(context).pop();
            },
          ),
        ],
      ),
    );
  }
}

Здесь MScaffold.dart:

import 'package:flutter/cupertino.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';

class ShowSnackBar extends ChangeNotifier {
  SnackBar currentSnackBar;
  int lastHideTime = -1; //in millisecondsSinceEpoch
  String _msg;
  bool isSnackBarVisible = false;
  static final _thisClass = ShowSnackBar._internal();
  ShowSnackBar._internal();
  factory ShowSnackBar() {
    return _thisClass;
  }
  ChangeNotifier showNotifier = ChangeNotifier();
  ChangeNotifier hideNotifier = ChangeNotifier();

  showText(String inputMsg) {
    _msg = inputMsg;
    currentSnackBar = SnackBar(content: Text(_msg));
    isSnackBarVisible = true;
    showNotifier.notifyListeners();
  }

  show(SnackBar inputSnackBar) {
    currentSnackBar = inputSnackBar;
    isSnackBarVisible = true;
    showNotifier.notifyListeners();
  }

  hide() {
    hideNotifier.notifyListeners();
  }
}

class MScaffold extends Scaffold {
  ValueKey key;
  var appBar;
  var body;
  var floatingActionButton;
  var floatingActionButtonLocation;
  var floatingActionButtonAnimator;
  var persistentFooterButtons;
  var drawer;
  var endDrawer;
  var bottomNavigationBar;
  var bottomSheet;
  var backgroundColor;
  var resizeToAvoidBottomPadding;
  var resizeToAvoidBottomInset;
  var primary;
  var drawerDragStartBehavior;
  var extendBody;
  var extendBodyBehindAppBar;
  var drawerScrimColor;
  var drawerEdgeDragWidth;

  MScaffold({
    this.key,
    this.appBar,
    this.body,
    this.floatingActionButton,
    this.floatingActionButtonLocation,
    this.floatingActionButtonAnimator,
    this.persistentFooterButtons,
    this.drawer,
    this.endDrawer,
    this.bottomNavigationBar,
    this.bottomSheet,
    this.backgroundColor,
    this.resizeToAvoidBottomPadding,
    this.resizeToAvoidBottomInset,
    this.primary = true,
    this.drawerDragStartBehavior = DragStartBehavior.start,
    this.extendBody = false,
    this.extendBodyBehindAppBar = false,
    this.drawerScrimColor,
    this.drawerEdgeDragWidth,
  })  : assert(primary != null),
        assert(extendBody != null),
        assert(extendBodyBehindAppBar != null),
        assert(drawerDragStartBehavior != null),
        assert(
          !((key!=null
            &&key.value is Map<String,dynamic>)
            &&key.value.length==1
            &&key.value.containsKey('MScaffoldAutoKey')),
          "The Key you use for MScaffold cannot be a Map object that contains only one index "
            "named, 'MScaffoldAutoKey,' as this is reserved for MScaffold."
        ),
        super(key: key) {
    if (key == null) this.key = _autoKeyGen();
  }

  static List<Key> _autoKeys = [];
  bool _usesAutoKey = false;
  Key _autoKeyGen() {
    _usesAutoKey = true;
    Key retKey = ValueKey({'MScaffoldAutoKey': _autoKeys.length});
    _autoKeys.add(retKey);
    return retKey;
  }

  @override
  MScaffoldState createState() {
    return MScaffoldState(
      key: key,
      appBar: appBar,
      body: body,
      floatingActionButton: floatingActionButton,
      floatingActionButtonLocation: floatingActionButtonLocation,
      floatingActionButtonAnimator: floatingActionButtonAnimator,
      persistentFooterButtons: persistentFooterButtons,
      drawer: drawer,
      endDrawer: endDrawer,
      bottomNavigationBar: bottomNavigationBar,
      bottomSheet: bottomSheet,
      backgroundColor: backgroundColor,
      resizeToAvoidBottomPadding: resizeToAvoidBottomPadding,
      resizeToAvoidBottomInset: resizeToAvoidBottomInset,
      primary: primary,
      drawerDragStartBehavior: drawerDragStartBehavior,
      extendBody: extendBody,
      extendBodyBehindAppBar: extendBodyBehindAppBar,
      drawerScrimColor: drawerScrimColor,
      drawerEdgeDragWidth: drawerEdgeDragWidth,
      autoKeys: _usesAutoKey ? _autoKeys : null,
    );
  }
}

class MScaffoldState extends ScaffoldState {
  Key key;
  var appBar;
  var body;
  var floatingActionButton;
  var floatingActionButtonLocation;
  var floatingActionButtonAnimator;
  var persistentFooterButtons;
  var drawer;
  var endDrawer;
  var bottomNavigationBar;
  var bottomSheet;
  var backgroundColor;
  var resizeToAvoidBottomPadding;
  var resizeToAvoidBottomInset;
  var primary;
  var drawerDragStartBehavior;
  var extendBody;
  var extendBodyBehindAppBar;
  var drawerScrimColor;
  var drawerEdgeDragWidth;
  var autoKeys;

  MScaffoldState({
    this.key,
    this.appBar,
    this.body,
    this.floatingActionButton,
    this.floatingActionButtonLocation,
    this.floatingActionButtonAnimator,
    this.persistentFooterButtons,
    this.drawer,
    this.endDrawer,
    this.bottomNavigationBar,
    this.bottomSheet,
    this.backgroundColor,
    this.resizeToAvoidBottomPadding,
    this.resizeToAvoidBottomInset,
    this.primary = true,
    this.drawerDragStartBehavior = DragStartBehavior.start,
    this.extendBody = false,
    this.extendBodyBehindAppBar = false,
    this.drawerScrimColor,
    this.drawerEdgeDragWidth,
    this.autoKeys,
  })  : assert(primary != null),
        assert(extendBody != null),
        assert(extendBodyBehindAppBar != null),
        assert(drawerDragStartBehavior != null);

  Function() _listenerShow;
  Function() _listenerHide;

  @override
  void initState() {
    super.initState();
    _listenerShow = () {
      if (mounted) {
        Scaffold.of(_scaffoldContext)
            .showSnackBar(ShowSnackBar().currentSnackBar)
            .closed
            .then((SnackBarClosedReason reason) {
          ShowSnackBar().isSnackBarVisible = false;
          ShowSnackBar().lastHideTime = DateTime.now().millisecondsSinceEpoch;
        });
      }
    };

    _listenerHide = () {
      if (mounted) {
        Scaffold.of(_scaffoldContext).hideCurrentSnackBar();
      }
    };

    Future.microtask(() {
      if (ShowSnackBar().isSnackBarVisible) _listenerShow();
      ShowSnackBar().showNotifier.addListener(_listenerShow);
      ShowSnackBar().hideNotifier.addListener(_listenerHide);
    });
  }

  @override
  dispose() {
    ShowSnackBar().showNotifier?.removeListener(_listenerShow);
    ShowSnackBar().hideNotifier?.removeListener(_listenerHide);
    autoKeys?.remove(key);
    super.dispose();
  }

  BuildContext _scaffoldContext;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      key: key,
      appBar: appBar,
      body: Builder(
        builder: (context) {
          _scaffoldContext = context;
          return body;
        },
      ),
      floatingActionButton: floatingActionButton,
      floatingActionButtonLocation: floatingActionButtonLocation,
      floatingActionButtonAnimator: floatingActionButtonAnimator,
      persistentFooterButtons: persistentFooterButtons,
      drawer: drawer,
      endDrawer: endDrawer,
      bottomNavigationBar: bottomNavigationBar,
      bottomSheet: bottomSheet,
      backgroundColor: backgroundColor,
      resizeToAvoidBottomPadding: resizeToAvoidBottomPadding,
      resizeToAvoidBottomInset: resizeToAvoidBottomInset,
      primary: primary,
      drawerDragStartBehavior: drawerDragStartBehavior,
      extendBody: extendBody,
      extendBodyBehindAppBar: extendBodyBehindAppBar,
      drawerScrimColor: drawerScrimColor,
      drawerEdgeDragWidth: drawerEdgeDragWidth,
    );
  }
}
...