Представьте себе такой сценарий.
Например: целая куча фильмов разных жанров. По идее это TabBar и в каждой вкладке есть список mov ie жанра. В этом случае ChangeNotifier
, например, MoviesBloc
будет в значительной степени удовлетворять потребности, ChangeNotifier
не имеет ничего общего с Genre
. В каждом дочернем элементе TabBarView использование ChangeNotifierProvider.value
здесь неправильно, поскольку каждая вкладка должна содержать свое собственное состояние MoviesBloc
, поэтому я предоставлю ChangeNotifierProvider
из MoviesBloc
для каждого жанра, а затем Consumer
для его прослушивания. , Я поместил их в класс-оболочку с именем MoviesBlocView
.
Результат: - При последовательном перелистывании каждой вкладки ошибки не возникает. - При смахивании на большое количество вкладок. Например: внезапно от 1-й вкладки до последней вкладки (например, есть 20 вкладок), консоль будет жаловаться на повторное использование удаленного ChangeNotifier
, хотя каждая вкладка создается со своим собственным ChangeNotifier
отдельно.
Код для воспроизведения
movies_bloc.dart
import 'package:flutter/material.dart';
class MoviesBloc extends ChangeNotifier {
List<Movie> _result;
BlocState _state = BlocState.idle;
Future<void> getMoviesWithGenre(Genre genre) async {
_setState(BlocState.loading);
await Future.delayed(_delayTime);
_result = List.generate(
20, (index) => Movie(id: index + 1, name: (index + 1).toString()));
_setState(BlocState.loaded);
}
List<Movie> get result => _result;
bool get loading => _state == BlocState.loading;
BlocState get state => _state;
void _setState(BlocState state) {
_state = state;
notifyListeners();
}
}
enum BlocState {
idle,
loading,
loaded,
}
class Movie {
Movie({
this.id,
this.name,
});
num id;
String name;
}
class Genre {
Genre({
this.id,
this.name,
});
num id;
String name;
}
const _delayTime = Duration(seconds: 2);
movies_bloc_view.dart
class MoviesBlocView extends StatelessWidget {
const MoviesBlocView({
Key key,
@required this.bloc,
@required this.loadedBuilder,
}) : assert(bloc != null),
assert(loadedBuilder != null),
super(key: key);
final MoviesBloc bloc;
final Widget Function(BuildContext context, List<Movie> result) loadedBuilder;
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider<MoviesBloc>(
create: (context) => bloc,
child: Consumer<MoviesBloc>(
builder: (context, bloc, child) {
switch (bloc.state) {
case BlocState.idle:
return Container();
case BlocState.loading:
return const Center(child: CircularProgressIndicator());
case BlocState.loaded:
return loadedBuilder(context, bloc.result);
default:
return Container();
}
},
),
);
}
}
main.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'movies_bloc.dart';
import 'movies_bloc_view.dart';
List<Genre> genres = List.generate(
20, (index) => Genre(id: index + 1, name: (index + 1).toString()));
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: DefaultTabController(
length: genres.length,
child: NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
SliverAppBar(
forceElevated: innerBoxIsScrolled,
title: const Text('Provider'),
bottom: TabBar(
isScrollable: true,
tabs: genres
.map((genre) => Tab(child: Text(genre.name)))
.toList(growable: false),
),
),
];
},
body: TabBarView(
children: genres.map(
(genre) {
// use wrapper
return MoviesBlocView(
bloc: MoviesBloc()..getMoviesWithGenre(genre),
loadedBuilder: (context, movies) => MoviesView(
movies: movies,
genre: genre,
),
);
// or use provider directly
return ChangeNotifierProvider<MoviesBloc>(
create: (context) => MoviesBloc()..getMoviesWithGenre(genre),
child: Consumer<MoviesBloc>(
builder: (context, bloc, child) {
switch (bloc.state) {
case BlocState.idle:
return Container();
case BlocState.loading:
return const Center(
child: CircularProgressIndicator());
case BlocState.loaded:
return MoviesView(
movies: bloc.result,
genre: genre,
);
default:
return Container();
}
},
),
);
},
).toList(growable: false),
),
),
),
);
}
}
class MoviesView extends StatefulWidget {
const MoviesView({
Key key,
@required this.movies,
@required this.genre,
}) : assert(movies != null),
assert(genre != null),
super(key: key);
final List<Movie> movies;
final Genre genre;
@override
_MoviesViewState createState() => _MoviesViewState();
}
class _MoviesViewState extends State<MoviesView>
with AutomaticKeepAliveClientMixin {
@override
bool get wantKeepAlive => true;
@override
Widget build(BuildContext context) {
super.build(context); // AutomaticKeepAliveClientMixin
return ListView.builder(
shrinkWrap: true,
itemCount: widget.movies.length,
itemBuilder: (_, index) {
return ListTile(
title: Text('Movie: ${widget.movies[index].name}'),
trailing: Text('Genre: ${widget.genre.name}'),
);
},
);
}
}
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: HomePage(),
);
}
}
Обновление :
- Если мы используем оболочку
MoviesBlocView
, независимо от того, вызываем ли getMoviesWithGenre
при MoviesBloc
создании или нет, консоль все равно жалуется
════════ Exception caught by widgets library ═══════════════════════════════════
The following assertion was thrown building Consumer<MoviesBloc>(dirty, dependencies: [_DefaultInheritedProviderScope<MoviesBloc>]):
A MoviesBloc was used after being disposed.
Once you have called dispose() on a MoviesBloc, it can no longer be used.
The relevant error-causing widget was
Consumer<MoviesBloc>
lib\test_tabs\movies_bloc_view.dart:23
When the exception was thrown, this was the stack
#0 ChangeNotifier._debugAssertNotDisposed.<anonymous closure>
package:flutter/…/foundation/change_notifier.dart:105
#1 ChangeNotifier._debugAssertNotDisposed
package:flutter/…/foundation/change_notifier.dart:111
#2 ChangeNotifier.addListener
package:flutter/…/foundation/change_notifier.dart:141
#3 ListenableProvider._startListening
package:provider/src/listenable_provider.dart:87
#4 _CreateInheritedProviderState.value
package:provider/src/inherited_provider.dart:433
...
Если мы не используем
MoviesBlocView
, консоль
- будет жаловаться при попытке вызвать
getMoviesWithGenre
при создании MoviesBloc
. Странно, что теперь у него есть другой журнал ошибок
E/flutter (30300): [ERROR:flutter/lib/ui/ui_dart_state.cc(157)] Unhandled Exception: A MoviesBloc was used after being disposed.
E/flutter (30300): Once you have called dispose() on a MoviesBloc, it can no longer be used.
[38;5;244mE/flutter (30300): #0 ChangeNotifier._debugAssertNotDisposed.<anonymous closure>[39;49m
[38;5;244mE/flutter (30300): #1 ChangeNotifier._debugAssertNotDisposed[39;49m
[38;5;244mE/flutter (30300): #2 ChangeNotifier.notifyListeners[39;49m
[38;5;248mE/flutter (30300): #3 MoviesBloc._setState[39;49m
[38;5;248mE/flutter (30300): #4 MoviesBloc.getMoviesWithGenre[39;49m
E/flutter (30300): <asynchronous suspension>
[38;5;248mE/flutter (30300): #5 HomePage.build.<anonymous closure>.<anonymous closure>[39;49m
[38;5;248mE/flutter (30300): #6 _CreateInheritedProviderState.value[39;49m
[38;5;248mE/flutter (30300): #7 _InheritedProviderScopeMixin.value[39;49m
[38;5;248mE/flutter (30300): #8 Provider.of[39;49m
[38;5;248mE/flutter (30300): #9 Consumer.buildWithChild[39;49m
[38;5;248mE/flutter (30300): #10 SingleChildStatelessWidget.build[39;49m
[38;5;244mE/flutter (30300): #11 StatelessElement.build[39;49m
[38;5;248mE/flutter (30300): #12 SingleChildStatelessElement.build[39;49m
[38;5;244mE/flutter (30300): #13 ComponentElement.performRebuild[39;49m
[38;5;244mE/flutter (30300): #14 Element.rebuild[39;49m
[38;5;244mE/flutter (30300): #15 ComponentElement._firstBuild[39;49m
[38;5;244mE/flutter (30300): #16 ComponentElement.mount[39;49m
[38;5;248mE/flutter (30300): #17 SingleChildWidgetElementMixin.mount[39;49m
[38;5;244mE/flutter (30300): #18 Element.inflateWidget[39;49m
[38;5;244mE/flutter (30300): #19 Element.updateChild[39;49m
[38;5;244mE/flutter (30300): #20 ComponentElement.performRebuild[39;49m
[38;5;248mE/flutter (30300): #21 _InheritedProviderScopeMixin.performRebuild[39;49m
[38;5;244mE/flutter (30300): #22 Element.rebuild[39;49m
[38;5;244mE/flutter (30300): #23 ComponentElement._firstBuild[39;49m
[38;5;244mE/flutter (30300): #24 ComponentElement.mount[39;49m
[38;5;244mE/flutter (30300): #25 Element.inflateWidget[39;49m
[38;5;244mE/flutter (30300): #26 Element.updateChild[39;49m
[38;5;244mE/flutter (30300): #27 ComponentElement.performRebuild[39;49m
[38;5;244mE/flutter (30300): #28 Element.rebuild[39;49m
[38;5;244mE/flutter (30300): #29 ComponentElement._firstBuild[39;49m
[38;5;244mE/flutter (30300): #30 ComponentElement.mount[39;49m
[38;5;248mE/flutter (30300): #31 SingleChildWidgetElementMixin.mount[39;49m
[38;5;244mE/flutter (30300): #32 Element.inflateWidget[39;49m
[38;5;244mE/flutter (30300): #33 Element.updateChild[39;49m
[38;5;244mE/flutter (30300): #34 ComponentElement.performRebuild[39;49m
[38;5;244mE/flutter (30300): #35 Element.rebuild[39;49m
[38;5;244mE/flutter (30300): #36 ComponentElement._firstBuild[39;49m
[38;5;244mE/flutter (30300): #37 ComponentElement.mount[39;49m
[38;5;244mE/flutter (30300): #38 Element.inflateWidget[39;49m
[38;5;244mE/flutter (30300): #39 Element.updateChild[39;49m
[38;5;244mE/flutter (30300): #40 SingleChildRenderObjectElement.mount[39;49m
[38;5;244mE/flutter (30300): #41 Element.inflateWidget[39;49m
[38;5;244mE/flutter (30300): #42 Element.updateChild[39;49m
E/flutter (30300): #43 SingleChildRenderObjectElement.mount (package:flutter/sr
E/flutter (30300): [ERROR:flutter/lib/ui/ui_dart_state.cc(157)] Unhandled Exception: A MoviesBloc was used after being disposed.
E/flutter (30300): Once you have called dispose() on a MoviesBloc, it can no longer be used.
[38;5;244mE/flutter (30300): #0 ChangeNotifier._debugAssertNotDisposed.<anonymous closure>[39;49m
[38;5;244mE/flutter (30300): #1 ChangeNotifier._debugAssertNotDisposed[39;49m
[38;5;244mE/flutter (30300): #2 ChangeNotifier.notifyListeners[39;49m
[38;5;248mE/flutter (30300): #3 MoviesBloc._setState[39;49m
[38;5;248mE/flutter (30300): #4 MoviesBloc.getMoviesWithGenre[39;49m
E/flutter (30300): <asynchronous suspension>
[38;5;248mE/flutter (30300): #5 HomePage.build.<anonymous closure>.<anonymous closure>[39;49m
[38;5;248mE/flutter (30300): #6 _CreateInheritedProviderState.value[39;49m
[38;5;248mE/flutter (30300): #7 _InheritedProviderScopeMixin.value[39;49m
[38;5;248mE/flutter (30300): #8 Provider.of[39;49m
[38;5;248mE/flutter (30300): #9 Consumer.buildWithChild[39;49m
[38;5;248mE/flutter (30300): #10 SingleChildStatelessWidget.build[39;49m
[38;5;244mE/flutter (30300): #11 StatelessElement.build[39;49m
[38;5;248mE/flutter (30300): #12 SingleChildStatelessElement.build[39;49m
[38;5;244mE/flutter (30300): #13 ComponentElement.performRebuild[39;49m
[38;5;244mE/flutter (30300): #14 Element.rebuild[39;49m
[38;5;244mE/flutter (30300): #15 ComponentElement._firstBuild[39;49m
[38;5;244mE/flutter (30300): #16 ComponentElement.mount[39;49m
[38;5;248mE/flutter (30300): #17 SingleChildWidgetElementMixin.mount[39;49m
[38;5;244mE/flutter (30300): #18 Element.inflateWidget[39;49m
[38;5;244mE/flutter (30300): #19 Element.updateChild[39;49m
[38;5;244mE/flutter (30300): #20 ComponentElement.performRebuild[39;49m
[38;5;248mE/flutter (30300): #21 _InheritedProviderScopeMixin.performRebuild[39;49m
[38;5;244mE/flutter (30300): #22 Element.rebuild[39;49m
[38;5;244mE/flutter (30300): #23 ComponentElement._firstBuild[39;49m
[38;5;244mE/flutter (30300): #24 ComponentElement.mount[39;49m
[38;5;244mE/flutter (30300): #25 Element.inflateWidget[39;49m
[38;5;244mE/flutter (30300): #26 Element.updateChild[39;49m
[38;5;244mE/flutter (30300): #27 ComponentElement.performRebuild[39;49m
[38;5;244mE/flutter (30300): #28 Element.rebuild[39;49m
[38;5;244mE/flutter (30300): #29 ComponentElement._firstBuild[39;49m
[38;5;244mE/flutter (30300): #30 ComponentElement.mount[39;49m
[38;5;248mE/flutter (30300): #31 SingleChildWidgetElementMixin.mount[39;49m
[38;5;244mE/flutter (30300): #32 Element.inflateWidget[39;49m
[38;5;244mE/flutter (30300): #33 Element.updateChild[39;49m
[38;5;244mE/flutter (30300): #34 ComponentElement.performRebuild[39;49m
[38;5;244mE/flutter (30300): #35 Element.rebuild[39;49m
[38;5;244mE/flutter (30300): #36 ComponentElement._firstBuild[39;49m
[38;5;244mE/flutter (30300): #37 ComponentElement.mount[39;49m
[38;5;244mE/flutter (30300): #38 Element.inflateWidget[39;49m
[38;5;244mE/flutter (30300): #39 Element.updateChild[39;49m
[38;5;244mE/flutter (30300): #40 SingleChildRenderObjectElement.mount[39;49m
[38;5;244mE/flutter (30300): #41 Element.inflateWidget[39;49m
[38;5;244mE/flutter (30300): #42 Element.updateChild[39;49m
E/flutter (30300): #43 SingleChildRenderObjectElement.mount (package:flutter/sr
- без ошибок, когда мы не вызываем
getMoviesWithGenre
при MoviesBloc
создании, но это не имеет никакого смысла, потому что если мы не делаем каскадирование таким образом, то я не знаю, как сделать, например, запрос на создание .
Любая помощь признателен!
Обновление
После некоторой отладки я обнаружил, что это из-за странного поведения TabBar. Он также пытается предварительно загрузить ближайшую выбранную вкладку или что-то еще, а затем избавиться от нее, а не один, два раза подряд в одной и той же вкладке.
Как Реми указал, что мне нужно проверить мою MoviesBloc
не вызывает notifyListeners () при его удалении, после того, как я добавил эту проверку в мой MoviesBloc
, он работает нормально
bool _mounted = true;
bool get mounted => _mounted;
@override
void dispose() {
_mounted = false;
super.dispose();
}
void _setState(BlocState state) {
if (!mounted) return;
_state = state;
notifyListeners();
}
Но это не то, что я ожидал, поведение MoviesBloc
так как я вызываю функцию при создании, чтобы никогда не предполагать, что она будет вызывать функцию после ее удаления.
I/flutter ( 1431): MoviesBloc - getMoviesWithGenre: 16
I/flutter ( 1431): MoviesBloc - getMoviesWithGenre: 16
I/flutter ( 1431): MoviesBloc - getMoviesWithGenre: 17 --> selected tab
I/flutter ( 1431): MoviesBloc - getMoviesWithGenre: 19
I/flutter ( 1431): MoviesBloc - getMoviesWithGenre: 20 --> selected tab
I/flutter ( 1431): MoviesBloc - getMoviesWithGenre: 19
I/flutter ( 1431): MoviesBloc - getMoviesWithGenre: 8
I/flutter ( 1431): MoviesBloc - getMoviesWithGenre: 7 --> selected tab
I/flutter ( 1431): MoviesBloc - getMoviesWithGenre: 8
На данный момент, я действительно не знаю, что происходит с TabBar & TabBarView. Если кто-нибудь может объяснить, почему TabBar делает это, я буду очень признателен!