Flutter: как использовать InheritedWidget без плагинов? - PullRequest
0 голосов
/ 13 апреля 2020

Введение: Я считаю, что эта проблема в принципе может быть общей, но я чувствую, что это конкретное c приложение не является. Я смиренно представляю это на рассмотрение, поэтому, пожалуйста, будьте нежны. Я наткнулся на эту ловушку с моим кодом и просто не знаю, как это исправить. Подумав об этом часами, я даже понятия не имею, что делать.

Проблема: InheritedWidget не обновляет состояние в виджете над ним.

Ожидание: Я ожидаю, что когда я проведу пальцем по PageView HomePage , Text из BottomAppBar будет обновлен. Однако этого не происходит.

Описание: Я пытаюсь использовать InheritedWidget ( InheritedPageIndex ), который я инициализировал над * Виджет 1099 * ( MaterialApp ) через a Провайдер (см. Код ниже). У которого есть значение, которое передается в виджет под ним ( MenuPage ) в дереве виджетов. Я хочу, чтобы это значение обновлялось при изменении состояния; которую я пытаюсь изменить, проводя пальцем по PageView ( HomePage ) в виджете под ним в дереве виджетов.

enter image description here

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

page_index_model.dart

import 'package:flutter/material.dart';

    class PageIndex {
      PageIndex({@required this.index});
      double index = 0;
    }

page_index_provider.dart

import 'package:flutter/cupertino.dart';
import 'package:testinginheritedwidget/models/page_index_model.dart';

class InheritedPageIndex extends InheritedWidget {
  InheritedPageIndex({Key key, @required this.indexData, Widget child})
      : super(key: key, child: child);

  final PageIndex indexData;

  @override
  bool updateShouldNotify(InheritedWidget oldWidget) => true;

  static PageIndex of(BuildContext context) => context
      .dependOnInheritedWidgetOfExactType<InheritedPageIndex>()
      .indexData;
}

main .dart

import 'package:flutter/material.dart';
import 'package:testinginheritedwidget/models/page_index_model.dart';
import 'package:testinginheritedwidget/pages/menu_page.dart';
import 'package:testinginheritedwidget/providers/page_index_provider.dart';

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

    class App extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return InheritedPageIndex(
          indexData: PageIndex(index: 0),
          child: MaterialApp(
            debugShowCheckedModeBanner: false,
            title: 'Testing Inherited Widgets',
            home: MenuPage(),
          ),
        );
      }
    }

menu_page.dart

    import 'package:flutter/material.dart';
    import 'package:testinginheritedwidget/models/page_index_model.dart';
    import 'package:testinginheritedwidget/pages/home_page.dart';
    import 'package:testinginheritedwidget/providers/page_index_provider.dart';

    class MenuPage extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        PageIndex _pageIndex = InheritedPageIndex.of(context);

        return DefaultTabController(
          length: 3,
          child: Scaffold(
            //TODO: dot indicator
            resizeToAvoidBottomInset: false,
            appBar: AppBar(
              backgroundColor: Colors.black,
              title: Center(
                child: Text('Testing Inherited Widgets',
                    style: TextStyle(fontSize: 20.0, fontWeight: FontWeight.bold)),
              ),
              bottom: TabBar(
                onTap: (tabIndex) {},
                labelColor: Colors.white,
                indicatorColor: Colors.white,
                tabs: const <Widget>[
                  Tab(icon: Icon(Icons.flight), child: Text('Flight')),
                  Tab(icon: Icon(Icons.map), child: Text('Map')),
                  Tab(icon: Icon(Icons.print), child: Text('Print')),
                ],
              ),
            ),
            body: SafeArea(
              child: TabBarView(
                children: <Widget>[
                  HomePage(),
                  HomePage(),
                  HomePage(),
                ],
              ),
            ),
            bottomNavigationBar: BottomAppBar(
              child: Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
// *** PROBLEM 1 IS HERE ***
                  Text('${_pageIndex.index}'),
                ],
              ),
            ),
          ),
        );
      }
    }

home_page.dart

    import 'package:flutter/material.dart';
    import 'package:testinginheritedwidget/models/page_index_model.dart';
    import 'package:testinginheritedwidget/providers/page_index_provider.dart';

    class HomePage extends StatefulWidget {
      @override
      _HomePageState createState() => _HomePageState();
    }

    class _HomePageState extends State<HomePage> {
      List<Widget> cards = [];

      @override
      Widget build(BuildContext context) {
        PageIndex _pageIndex = InheritedPageIndex.of(context);

        for (var index = 0; index < 7; index++) {
          cards.add(
            Center(
              child: Text(
                '$index',
                style: TextStyle(fontSize: 25, fontWeight: FontWeight.bold),
              ),
            ),
          );
          print(index);
        }
        return PageView(
          onPageChanged: (index) {
            setState(() {
//*** PROBLEM 2 IS HERE ***
              _pageIndex.index = index.toDouble();
              print('page index: ${_pageIndex.index}');
            });
          },
          children: cards,
        );
      }
    }

Предположение: После долгих исследований и размышлений. Я считаю, что я не могу обновить значение провайдера; потому что он находится между первым экземпляром InheritedWidget и методом, который изменяет состояние. Возможно, другой способ сказать, что государство, которое я хочу изменить, находится прямо над ним; и вместо этого он принимает состояние второго провайдера вместо использования состояния первого?

Вопросы:

1) В мою пользу, я прав в своем предположении , или здесь что-то еще происходит?

2) Как я могу решить мою проблему? Не знаю Полагаете, что реализация метода BLo C решит эту проблему, потому что он тоже использует провайдера?

Я не знаю, как я передам состояние в дереве виджетов в этом случае без InheritedWidget.

Ответы [ 3 ]

1 голос
/ 13 апреля 2020

Вы должны проверить этот пакет: https://pub.dev/packages/provider, который является оболочкой InheritedWidget. И проверьте ChangeNotiferProvider , который предоставляет класс ChangeNotifier и позволяет уведомлять слушателей при обновлении. Я доступен, если вам нужно больше объяснений

0 голосов
/ 25 апреля 2020

Решение с пакетами

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

Код:

main.dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:stackoverflowquestionthree/counter_model.dart';
import 'package:stackoverflowquestionthree/page_menu.dart';

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

class App extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Testing Inherited Widgets',
      home: ChangeNotifierProvider(
        create: (BuildContext context) => Counter(0),
        child: MenuPage(),
      ),
    );
  }
}

counter_model.dart

import 'package:flutter/material.dart';

class Counter with ChangeNotifier {
  int _counter;

  Counter(this._counter);

  getCounter() => _counter;

  setCounter(int counter) => _counter = counter;

  void increment(int counter) {
    _counter = counter;
    notifyListeners();
  }
}

page_menu.dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:stackoverflowquestionthree/counter_model.dart';
import 'package:stackoverflowquestionthree/home_page.dart';

class MenuPage extends StatefulWidget {
  @override
  _MenuPageState createState() => _MenuPageState();
}

class _MenuPageState extends State<MenuPage> {
  var _bloc;

  @override
  Widget build(BuildContext context) {
    final counter = Provider.of<Counter>(context);

    return DefaultTabController(
      length: 3,
      child: Scaffold(
        //TODO: dot indicator
        resizeToAvoidBottomInset: false,
        appBar: AppBar(
          backgroundColor: Colors.black,
          title: Center(
            child: Text('Testing Inherited Widgets',
                style: TextStyle(fontSize: 20.0, fontWeight: FontWeight.bold)),
          ),
          bottom: TabBar(
            onTap: (tabIndex) {
              counter.increment(0);
            },
            labelColor: Colors.white,
            indicatorColor: Colors.white,
            tabs: const <Widget>[
              Tab(icon: Icon(Icons.flight), child: Text('Flight')),
              Tab(icon: Icon(Icons.map), child: Text('Map')),
              Tab(icon: Icon(Icons.print), child: Text('Print')),
            ],
          ),
        ),
        body: SafeArea(
          child: TabBarView(
            children: <Widget>[
              HomePage(),
              HomePage(),
              HomePage(),
            ],
          ),
        ),
        bottomNavigationBar: BottomAppBar(
          child: Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              //TODO: insert value below.
              Text(counter.getCounter().toString()),
            ],
          ),
        ),
      ),
    );
  }
}

home_page.dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:stackoverflowquestionthree/counter_model.dart';

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  List<Widget> cards = [];

  @override
  Widget build(BuildContext context) {
    final counter = Provider.of<Counter>(context);

    for (var index = 0; index < 7; index++) {
      cards.add(
        Center(
          child: Text(
            '$index',
            style: TextStyle(fontSize: 25, fontWeight: FontWeight.bold),
          ),
        ),
      );
      print(index);
    }
    return PageView(
      onPageChanged: (index) {
        setState(() {
          counter.increment(index);
        });
      },
      children: cards,
    );
  }
}
0 голосов
/ 14 апреля 2020

Решение без пакетов

Признание: Во-первых, спасибо вышеупомянутым людям, которые дали мне отличные предложения по решению моей проблемы. Их рекомендации обоснованы. Тем не менее, я пытаюсь понять основные принципы ошибки, прежде чем использовать пакеты и решить проблему таким образом. Я буду использовать ваши предложения, когда лучше пойму пакеты.

Введение: Я отвечаю на вопрос в интересах начинающих программистов, которые хотят понять, когда использовать BLo C шаблон вместо InheritedWidget. Этот ответ направлен на решение более глубокой проблемы моих первоначальных предположений и предлагает решение.

Ответы:

1) Использование унаследованного виджета для изменения состояния в нисходящем направлении или в другой ветке дерева виджетов нормально; однако, это ужасная идея - использовать InheritedWidgets, которые находятся прямо над деревом виджетов. Мой исходный код, кажется, пример из учебника, что не делать. Итак, мне пришлось изменить код.

2) Хотя InheritedWidget не может изменить состояние другого InheritedWidget непосредственно над ним в дереве виджетов; вместо этого будет работать шаблон BLo C.

Мое первоначальное предположение было ложным:

'Я не верю, что реализация Метод BLo C решил бы эту проблему, потому что он тоже использует провайдера? '

Это предположение было совершенно неверным, поскольку BLo C использует StreamController для обеспечения альтернативного способа изменения состояния, которое обходит InheritedWidget в дереве виджетов. Вместо этого BLo C получает событие ( см. Change_events.dart ). Таким образом, унаследованный виджет корректно обновляется в любом месте дерева виджетов.

Модифицированный код:

main.dart

import 'package:flutter/material.dart';
import 'package:testinginheritedwidget/pages/menu_page.dart';
import 'package:testinginheritedwidget/providers/page_index_provider.dart';

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

class App extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Provider(
      child: MaterialApp(
        debugShowCheckedModeBanner: false,
        title: 'Testing Inherited Widgets',
        home: MenuPage(),
      ),
    );
  }
}

menu_page.dart

import 'package:flutter/material.dart';
import 'package:testinginheritedwidget/bloc/page_index_bloc.dart';
import 'package:testinginheritedwidget/models/page_index_model.dart';
import 'package:testinginheritedwidget/pages/home_page.dart';
import 'package:testinginheritedwidget/providers/page_index_provider.dart';
class MenuPage extends StatefulWidget {
  @override
  _MenuPageState createState() => _MenuPageState();
}
class _MenuPageState extends State<MenuPage> {
  var _bloc;
  @override
  Widget build(BuildContext context) {
    PageIndexBloc _bloc = Provider.of(context);
    return DefaultTabController(
      length: 3,
      child: Scaffold(
        //TODO: dot indicator
        resizeToAvoidBottomInset: false,
        appBar: AppBar(
          backgroundColor: Colors.black,
          title: Center(
            child: Text('Testing Inherited Widgets',
                style: TextStyle(fontSize: 20.0, fontWeight: FontWeight.bold)),
          ),
          bottom: TabBar(
            onTap: (tabIndex) {},
            labelColor: Colors.white,
            indicatorColor: Colors.white,
            tabs: const <Widget>[
              Tab(icon: Icon(Icons.flight), child: Text('Flight')),
              Tab(icon: Icon(Icons.map), child: Text('Map')),
              Tab(icon: Icon(Icons.print), child: Text('Print')),
            ],
          ),
        ),
        body: SafeArea(
          child: TabBarView(
            children: <Widget>[
              HomePage(),
              HomePage(),
              HomePage(),
            ],
          ),
        ),
        bottomNavigationBar: StreamBuilder(
          stream: _bloc.pageModelStream,
          initialData: PageIndexModel(),
          builder:
              (BuildContext context, AsyncSnapshot<PageIndexModel> snapshot) {
            return BottomAppBar(
              child: Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  //TODO: insert value below.
                  Text('${snapshot.data.index}'),
                ],
              ),
            );
          },
        ),
      ),
    );
  }
}

home_page.dart

import 'package:flutter/material.dart';
import 'package:testinginheritedwidget/bloc/change_events.dart';
import 'package:testinginheritedwidget/models/page_index_model.dart';
import 'package:testinginheritedwidget/providers/page_index_provider.dart';

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  List<Widget> cards = [];

  @override
  Widget build(BuildContext context) {
    var _bloc = Provider.of(context);

    for (var index = 0; index < 7; index++) {
      cards.add(
        Center(
          child: Text(
            '$index',
            style: TextStyle(fontSize: 25, fontWeight: FontWeight.bold),
          ),
        ),
      );
      print(index);
    }
    return PageView(
      onPageChanged: (index) {
        setState(() {
          _bloc.changeEventsSink.add(PageChangeEvent(index: index));
        });
      },
      children: cards,
    );
  }
}

page_index_model.dart

class PageIndexModel {
  double index = 0;
}

page_index_blo c .dart

import 'dart:async';
import 'package:testinginheritedwidget/bloc/change_events.dart';
import 'package:testinginheritedwidget/models/page_index_model.dart';

class PageIndexBloc {
  PageIndexModel _indexModel = PageIndexModel();

  final _pageModelIndexStreamController = StreamController<PageIndexModel>();

  Sink<PageIndexModel> get _inPageIndex => _pageModelIndexStreamController.sink;
  Stream<PageIndexModel> get pageModelStream =>
      _pageModelIndexStreamController.stream;

  final _pageChangeEventController = StreamController<ChangeEvents>();
  Sink<ChangeEvents> get changeEventsSink => _pageChangeEventController.sink;

  PageIndexBloc() {
    _pageChangeEventController.stream.listen(_mapEventToState);
  }

  void _mapEventToState(ChangeEvents event) {
    if (event is PageChangeEvent) {
      print('triggered a page change event');
      _indexModel.index = event.index.toDouble();
    }
    _inPageIndex.add(_indexModel);
  }

  void dispose() {
    _pageModelIndexStreamController.close();
    _pageChangeEventController.close();
  }
}

change_events.dart

import 'dart:async';
import 'package:testinginheritedwidget/bloc/change_events.dart';
import 'package:testinginheritedwidget/models/page_index_model.dart';
class PageIndexBloc {
  PageIndexModel _indexModel = PageIndexModel();
  final _pageModelIndexStreamController = StreamController<PageIndexModel>();
  Sink<PageIndexModel> get _inPageIndex => _pageModelIndexStreamController.sink;
  Stream<PageIndexModel> get pageModelStream =>
      _pageModelIndexStreamController.stream;
  final _pageChangeEventController = StreamController<ChangeEvents>();
  Sink<ChangeEvents> get changeEventsSink => _pageChangeEventController.sink;
  PageIndexBloc() {
    _pageChangeEventController.stream.listen(_mapEventToState);
  }
  void _mapEventToState(ChangeEvents event) {
    if (event is PageChangeEvent) {
      print('triggered a page change event');
      _indexModel.index = event.index.toDouble();
    }
    _inPageIndex.add(_indexModel);
  }
  void dispose() {
    _pageModelIndexStreamController.close();
    _pageChangeEventController.close();
  }
}

page_index_provider.dart

import 'package:flutter/cupertino.dart';
import 'package:testinginheritedwidget/bloc/page_index_bloc.dart';

class Provider extends InheritedWidget {
  Provider({Key key, Widget child}) : super(key: key, child: child);

  final bloc = PageIndexBloc();

  @override
  bool updateShouldNotify(InheritedWidget oldWidget) => true;

  static PageIndexBloc of(BuildContext context) =>
      context.dependOnInheritedWidgetOfExactType<Provider>().bloc;
}
...