How to call dispose when using BLoC pattern and StatelessWidget - flutter

I am trying to understand BLoC pattern but I cannot figure out where or when to call dispose() in my example.
I am trying to understand various state management techniques in Flutter.
I came up with an example I managed to build with the use of StatefulWidget, scoped_model and streams.
I believe I finally figured out how to make my example work with the use of "BloC" pattern but I have a problem with calling the dispose() method as I use the StatelessWidgets only.
I tried converting PageOne and PageTwo to StatefulWidget and calling dispose() but ended up with closing the streams prematurely when moving between pages.
Is it possible I should not worry at all about closing the streams manually in my example?
import 'package:flutter/material.dart';
import 'dart:async';
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return StreamBuilder<ThemeData>(
initialData: bloc.themeProvider.getThemeData,
stream: bloc.streamThemeDataValue,
builder: (BuildContext context, AsyncSnapshot<ThemeData> snapshot) {
return MaterialApp(
title: 'bloc pattern example',
theme: snapshot.data,
home: BlocPatternPageOne(),
);
},
);
}
}
// -- page_one.dart
class BlocPatternPageOne extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('(block pattern) page one'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
buildRaisedButton(context),
buildSwitchStreamBuilder(),
],
),
),
);
}
StreamBuilder<bool> buildSwitchStreamBuilder() {
return StreamBuilder<bool>(
initialData: bloc.switchProvider.getSwitchValue,
stream: bloc.streamSwitchValue,
builder: (BuildContext context, AsyncSnapshot<bool> snapshot) {
return Switch(
value: snapshot.data,
onChanged: (value) {
bloc.sinkSwitchValue(value);
},
);
},
);
}
Widget buildRaisedButton(BuildContext context) {
return RaisedButton(
child: Text('go to page two'),
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) {
return BlocPatternPageTwo();
},
),
);
},
);
}
}
// -- page_two.dart
class BlocPatternPageTwo extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('(bloc pattern) page two'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
buildRaisedButton(context),
buildSwitchStreamBuilder(),
],
),
),
);
}
StreamBuilder<bool> buildSwitchStreamBuilder() {
return StreamBuilder<bool>(
initialData: bloc.switchProvider.getSwitchValue,
stream: bloc.streamSwitchValue,
builder: (BuildContext context, AsyncSnapshot<bool> snapshot) {
return Switch(
value: snapshot.data,
onChanged: (value) {
bloc.sinkSwitchValue(value);
},
);
},
);
}
Widget buildRaisedButton(BuildContext context) {
return RaisedButton(
child: Text('go back to page one'),
onPressed: () {
Navigator.of(context).pop();
},
);
}
}
// -- bloc.dart
class SwitchProvider {
bool _switchValue = false;
bool get getSwitchValue => _switchValue;
void updateSwitchValue(bool value) {
_switchValue = value;
}
}
class ThemeProvider {
ThemeData _themeData = ThemeData.light();
ThemeData get getThemeData => _themeData;
void updateThemeData(bool value) {
if (value) {
_themeData = ThemeData.dark();
} else {
_themeData = ThemeData.light();
}
}
}
class Bloc {
final StreamController<bool> switchStreamController =
StreamController.broadcast();
final SwitchProvider switchProvider = SwitchProvider();
final StreamController<ThemeData> themeDataStreamController =
StreamController();
final ThemeProvider themeProvider = ThemeProvider();
Stream get streamSwitchValue => switchStreamController.stream;
Stream get streamThemeDataValue => themeDataStreamController.stream;
void sinkSwitchValue(bool value) {
switchProvider.updateSwitchValue(value);
themeProvider.updateThemeData(value);
switchStreamController.sink.add(switchProvider.getSwitchValue);
themeDataStreamController.sink.add(themeProvider.getThemeData);
}
void dispose() {
switchStreamController.close();
themeDataStreamController.close();
}
}
final bloc = Bloc();
At the moment everything works, however, I wonder if I should worry about closing the streams manually or let Flutter handle it automatically.
If I should close them manually, when would you call dispose() in my example?

You can use provider package for flutter. It has callback for dispose where you can dispose of your blocs. Providers are inherited widgets and provides a clean way to manage the blocs. BTW I use stateless widgets only with provider and streams.

In stateless widget, there is not dispose method so you need not to worry about where to call it.
It's as simple as that

Related

Future builder runs forever, if memoizer used doesnt notify to the listerners

I am trying to switch the drawer tab, according to the value stored in shared preferences using the following code.
code works fine when memoizer is not used but future builder runs forever.
If I use memorizer future builder still runs at least two times (not forever), but get and set functions doesn't work and new values are not updated and are not notified to the widgets.
I need some way to stop running future builder forever and notify users as well accordingly by triggering get and set functions present in it
Notifier class
class SwitchAppProvider extends ChangeNotifier {
switchApp(value) async {
// initialize instance of sharedpreference
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setBool('key', value);
notifyListeners();
}
Future<bool?> getValue() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
final value = prefs.getBool('key');
return value;
}
}
Drawer
Widget _buildDrawer() {
return ChangeNotifierProvider<SwitchAppProvider>(
create: (context) => SwitchAppProvider(),
child: Consumer<SwitchAppProvider>(
builder: (context, provider, _) {
return Container(
width: 260,
child: Drawer(
child: Material(
color: Color.fromRGBO(62, 180, 137, 1),
child: ListView(
children: <Widget>[
Container(
padding: AppLandingView.padding,
child: Column(
children: [
const SizedBox(height: 10),
FutureBuilder(
future: provider.getValue(),
builder: (BuildContext context,
AsyncSnapshot<dynamic> snapshot) {
if (snapshot.data == true) {
return _buildMenuItem(
text: 'widget1',
icon: Icons.add_business,
onTap: () {
provider.switchApp(false);
},
);
} else {
return _buildMenuItem(
text: 'widget2',
icon: Icons.add_business,
onTap: () {
provider.switchApp(true);
},
);
}
},
),
],
),
),
],
),
),
),
);
},
),
);
}
Scaffold
#override
Widget build(BuildContext context) {
return Scaffold(
drawer: _buildDrawer(),
);
}
Update
I analysed further, problem lies in provider.getValue(), if i use notifyListeners() before returning the value future builder runs forever
I removed it and the future builder doesn't run forever, but it doesn't update other widgets.
Scenario is
widget 1
contains a drawer
has a button to switch app
on tap value is set using shared preferences (setValue() function) and listeners are notified
in widget 1 notifier is working well and changing the drawer button option when setValue() is called on tap.
everything resolved in widget 1, as its calling setValue() hence notifyListeners() is triggered and widget1 is rerendered
widget 2
only gets value from shared preferences(getValue() function). getValue function cant use notifyListeners(), if used futurebuilder is running forever
widget 2 don't set any value so it doesn't use setValue() hence it's not getting notified
how I can notify widget 2, when on tap setValue() is triggered in widget 1
i.e widget1 sets the app using setValue() function
widget2 gets value from getValue() function and get notified
Update 2
class SwitchAppProvider with ChangeNotifier {
dynamic _myValue;
dynamic get myValue => _myValue;
set myValue(dynamic newValue) {
_myValue = newValue;
notifyListeners();
}
setValue(value) async {
// initialize instance of sharedpreference
SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setBool('key', value);
notifyListeners();
}
SwitchAppProvider(){
getValue();
}
Future<void> getValue() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
myValue = prefs.getBool('key');
}
}
widget 2
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider.value(
value: SwitchAppProvider(),
child: Consumer<SwitchAppProvider>(
builder: (BuildContext context, SwitchAppProvider provider, _) {
if (provider.myValue == true) {
return Center(
child: CircularProgressIndicator(),
);
} else {
return Container(
child: Text('provider.myValue'));
}
})
);
}
}
_buildMenuItem
// helper widget to build item of drawer
Widget _buildMenuItem({
required String text,
required IconData icon,
required GestureTapCallback onTap,
}) {
final color = Colors.white;
final hoverColor = Colors.white;
return ListTile(
leading: Icon(icon, color: color),
title: Text(text, style: TextStyle(color: color, fontSize: 18)),
hoverColor: hoverColor,
onTap: onTap,
);
}
"If I use memorizer future builder still runs at least two times (not forever), but get and set functions doesn't work and new values are not updated and are not notified to the widgets."
That is the expected behaviour:
An AsyncMemoizer is used when some function may be run multiple times in order to get its result, but it only actually needs to be run once for its effect.
so prefs.setBool('key', value); is executed only the first time.
You definitely do not want to use it.
If you edit your code to remove the AsyncMemoizer, we can try to help you further.
Edit after Update
You are right, the getValue() function should not notify listeners, if it does that, then the listeners will rebuild and ask for the value again, which will notify listeners, which will rebuild and ask for the value again, which... (you get the point).
There is something wrong in your reasoning. widget1 and widget2 are not notified, the Consumer is notified. Which will rebuild everything. The code is quite complicated and it could be simplified a lot by removing unneeded widgets.
I will suggest you to
await prefs.setBool('isWhatsappBusiness', value); before notifying listeners.
have a look at this answer for a similar problem.
Edit 3
I do not know what you are doing wrong, but this works:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() {
runApp(const MyApp());
}
class SwitchAppProvider extends ChangeNotifier {
switchApp(value) async {
// initialize instance of sharedpreference
SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setBool('key', value);
notifyListeners();
}
Future<bool?> getValue() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
final value = prefs.getBool('key');
return value;
}
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(),
drawer: _buildDrawer(),
),
);
}
Widget _buildDrawer() {
return ChangeNotifierProvider<SwitchAppProvider>(
create: (context) => SwitchAppProvider(),
child: Consumer<SwitchAppProvider>(
builder: (context, provider, _) {
return SizedBox(
width: 260,
child: Drawer(
child: Material(
color: const Color.fromRGBO(62, 180, 137, 1),
child: ListView(
children: <Widget>[
Column(
children: [
const SizedBox(height: 10),
FutureBuilder(
future: provider.getValue(),
builder: (BuildContext context,
AsyncSnapshot<dynamic> snapshot) {
print('Am I building?');
if (snapshot.data == true) {
return ListTile(
tileColor: Colors.red[200],
title: const Text('widget1'),
leading: const Icon(Icons.flutter_dash),
onTap: () {
provider.switchApp(false);
},
);
} else {
return ListTile(
tileColor: Colors.green[200],
title: const Text('widget2'),
leading: const Icon(Icons.ac_unit),
onTap: () {
provider.switchApp(true);
},
);
}
},
),
],
),
],
),
),
),
);
},
),
);
}
}
If you still cannot get it working, then the problem is somewhere else.
Edit 4
First, I suggest you to be more clear in future questions. Write all the code that is needed immediately and remove widgets that are not needed. Avoid confusion given by naming different things in the same way.
The second widget does not update because it is listening to a different notifier.
When you do
return ChangeNotifierProvider.value(
value: SwitchAppProvider(),
in Widget2 you are creating a new provider object, you are not listening to changes in the provider you created in the Drawer.
You need to move the ChangeNotifierProvider.value widget higher in the widget tree, and use the same one:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() {
runApp(const MyApp());
}
class SwitchAppProvider extends ChangeNotifier {
switchApp(value) async {
// initialize instance of sharedpreference
SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setBool('key', value);
notifyListeners();
}
Future<bool?> getValue() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
final value = prefs.getBool('key');
return value;
}
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
home: ChangeNotifierProvider<SwitchAppProvider>(
create: (context) => SwitchAppProvider(),
child: Scaffold(
appBar: AppBar(),
drawer: _buildDrawer(),
body: const Widget2(),
),
),
);
}
Widget _buildDrawer() {
return Consumer<SwitchAppProvider>(builder: (context, provider, _) {
return SizedBox(
width: 260,
child: Drawer(
child: Material(
color: const Color.fromRGBO(62, 180, 137, 1),
child: ListView(
children: <Widget>[
Column(
children: [
const SizedBox(height: 10),
FutureBuilder(
future: provider.getValue(),
builder: (BuildContext context,
AsyncSnapshot<dynamic> snapshot) {
print('Am I building?');
if (snapshot.data == true) {
return ListTile(
tileColor: Colors.red[200],
title: const Text('widget1'),
leading: const Icon(Icons.flutter_dash),
onTap: () {
provider.switchApp(false);
},
);
} else {
return ListTile(
tileColor: Colors.green[200],
title: const Text('widget2'),
leading: const Icon(Icons.ac_unit),
onTap: () {
provider.switchApp(true);
},
);
}
},
),
],
),
],
),
),
),
);
});
}
}
class Widget2 extends StatelessWidget {
const Widget2({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Consumer<SwitchAppProvider>(
builder: (BuildContext context, SwitchAppProvider provider, _) {
return FutureBuilder(
future: provider.getValue(),
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
print('Am I building even more ?');
if (snapshot.data == true) {
return const Center(
child: CircularProgressIndicator(),
);
} else {
return const Text('provider.myValue');
}
},
);
},
);
}
}

Flutter Cubit Web

For retrieve items from FireStore and for pick image i am using cubit.
Cubit:
class ItemCubit extends Cubit<ItemState> {
ItemCubit(this._dataBase)
: super(ItemInitial());
final DataBase _dataBase;
StreamSubscription streamSubscription;
Future<void> pickItemImg() async {
final currentTempImg =
await ImagePickerWeb.getImage(outputType: ImageType.bytes);
emit(ItemImgPicked(currentTempImg));
}
Future getItem() async {
streamSubscription = _dataBase.getItem().listen((data) {
emit(ItemLoaded(data));
});
}
}
State:
#immutable
abstract class ItemState {}
class ItemLoaded extends ItemState {
final List<Item> item;
ItemLoaded(this.item);
}
class ItemImgPicked extends ItemState {
final Uint8List currentTempImg;
ItemImgPicked(this.currentTempImg);
}
Page with blocbuilders
class Page extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
RaisedButton(
onPressed: () async {
showDialog(
context: context,
builder: (BuildContext context) => Dialog(
child: Container(
width: 400,
child: OutlineButton(
onPressed: () async {
context.bloc<ItemCubit>().pickProductImg();
},
child: BlocBuilder<ItemCubit, ItemState>(
builder: (context, state) {
if (state is ItemImgPicked) {
return Image.memory(state.currentTempImg);
} else {
return Container();
}
},
),
),
),
),
);
},
child: Text('add'),
),
BlocBuilder<ItemCubit, ItemState>(
builder: (context, state) {
if (state is ItemLoaded) {
return Column(
children: state.item.map(
(item) {
return Text(item.name);
},
).toList(),
);
}
return CircularProgressIndicator();
},
)
],
),
);
}
}
Issue is when on show dialog I picked image, the picked image is displayed, but at the same time on main page blocbuilder for item list return CircularProgressIndicator. if I use hot reload at this time, after it shows me the list of item. It looks like the state for picked image replace state for item list. How to solve it?
Your main page bloc builder listens for ItemLoaded which you never emit as far as I can tell. You can put a breakpoint into that line, it should not get hit.
That said, please treat your async functions better, you missed to await some futures, that might not be your problem now, but it will become a problem sooner or later.

Bloc Library Null

In appbar I am trying to show profile icon after logged. When app start, appbar show profile icon, but at the same time in debug console give me an error 'A build function returned null'. When open profile page and return back, still the same error 'returned null' How to solve it?
class TestApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider(
create: (context) => AuthBloc(
authService: AuthService())
..add(
AppStart(),
),
),
],
child: MaterialApp(
home: HomePage(),
),
);
}
}
homepage:
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: <Widget>[
BlocBuilder<AuthBloc, AuthState>(
builder: (context, state) {
if (state is Authenticated) {
return profileIcon(context);
} else if (state is UnAuthenticated) {
return logIn(context);
}
},
),
],
),
);
}
}
bloc
#override
AuthState get initialState => AuthState();
#override
Stream<AuthState> mapEventToState(AuthEvent event) async* {
if (event is AppStart) {
try {
final user = await AuthService.getCurrentUser();
yield Authenticated(user: user);
} catch (e) {
yield UnAuthenticated();
}
}
}
icon:
Widget profileIcon(context) {
return Row(
children: <Widget>[
FlatButton.icon(
icon: Icon(),
label: Text(
'Profile',
),
onPressed: () {
Navigator.push(context,
MaterialPageRoute(builder: (context) => UserProfile()));
},
),
],
);
}
state:
class Authenticated extends AuthState {
final FirebaseUser user;
Authenticated({this.user});
#override
List<Object> get props => [user];
}
class UnAuthenticated extends AuthState {
#override
List<Object> get props => [];
}
I'm not really sure but my guess is you have another state for your initialState which is not handled in the BlocBuilder. Even though you add AppStart event right after providing AuthBloc which will end up with either Authenticated or UnAuthenticated state, you still need to put another else for your initialState. Even if it's not the case, try to add an else statement
appBar: AppBar(
actions: <Widget>[
BlocBuilder<AuthBloc, AuthState>(
builder: (context, state) {
if (state is Authenticated) {
return profileIcon(context);
} else if (state is UnAuthenticated) {
return logIn(context);
} else {
return Container();
}
},
),
],
),

How to add a CircularProgressIndicator while navigating to a PDF View Page

In the code below, I am reading a pdf file from my images folder then viewing it in a new page. But when I navigate to the new page it takes time to load the pdf. So, I want to add a CircularProgressIndicator while transitioning. I can't figure out how to do it. How do I add a CircularProgressIndicator widget?
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:path_provider/path_provider.dart';
import 'package:flutter_full_pdf_viewer/flutter_full_pdf_viewer.dart';
class PdfPage extends StatefulWidget {
#override
_PdfPageState createState() => _PdfPageState();
}
class _PdfPageState extends State<PdfPage> {
Future<String> makeFile() async {
Directory tempDir = await getTemporaryDirectory();
String tempPath = tempDir.path;
File tempFile = File('$tempPath/copy.pdf');
ByteData bd = await rootBundle.load('images/dummy.pdf');
await tempFile.writeAsBytes(bd.buffer.asUint8List(), flush: true);
return tempFile.path;
}
bool isLoading = false;
#override
void initState() {
// TODO: implement initState
super.initState();
makeFile().then((value) {
setState(() {
path = value;
print(path);
//isLoading = false;
});
});
}
String path;
#override
Widget build(BuildContext context) {
return Container(
child: Center(
child: RaisedButton(
child: Text("Open PDF Screen"),
onPressed: () {
// CircularProgressIndicator(value: true,);
Navigator.push(context,
MaterialPageRoute(builder: (context) => PDFScreen(path)));
},
),
),
);
}
}
class PDFScreen extends StatelessWidget {
final String path;
// final bool isLoading;
PDFScreen(
this.path,
);
#override
Widget build(BuildContext context) {
return PDFViewerScaffold(
appBar: AppBar(
title: Text("Scaffold PDF"),
),
path: path);
}
}
You can use FutureBuilder class which is precisely made for this. That is, some action is expected in the future and you would like to show a progress indicator meanwhile. The action may actually complete or return an error. Futurebuilder lets you handle all these scenarios.
You can shift makefile() into class PDFScreen and use widget build method to return the FutureBuilder. The simplest implementation is below.
#override
Widget build(BuildContext context) {
return FutureBuilder<String>(
future: makeFile(),
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
if (snapshot.connectionState != ConnectionState.done) {
return CircularProgressIndicator();
} else {
return PDFViewerScaffold(
appBar: AppBar(
title: Text("Scaffold PDF"),
),
path: snapshot.data
);
}
},
);
}
You might want to visit the official docuementation or this blog on Medium.
One way is to use it in an Dialog.
static Future showDialog(BuildContext context, GlobalKey _key) async {
return showLoadingDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context){
return SimpleDialog(
key: _key,
children: <Widget>[
Center(
child: Container(
child: Row(
children: <Widget>[
CircularProgressIndicator(),
SizedBox(
height:10,
width:10,
),
Text("Please Wait!"),
]
),
],
),
);
}
);
}
Here GlobalKey is used to Show or hide the dialog created.
To call this simple initialize a GlobalKey in the class as GlobalKey<State> _dialogKey = GlobalKey<State>();.
Then you can show the dialog as:
showLoadingDialog(context, _dialogKey);
And when you want it to hide, just call:
Navigator.of(_dialogKey.currentContext,rootNavigator: true).pop();
This is just one way of using CircularProgressIndicator in an SimpleDialog, Hope this helps.

Could not find the ancestor for consumer provider or provider could not be found

I have a multi provider config and I pass the providers in the mainApp and using the consumerProvider later. But I get the ancestor not found error. The same setup is working for another view but creating problems maybe because of the navigation
I have tried out some options that I found for similar problems in stackoverflow which stated moving the providers across and also looking at the context that is provided but did not find any solutions
First is my Provider.dart file
List<SingleChildCloneableWidget> providers = [
...independentServices,
...dependentServices,
];
List<SingleChildCloneableWidget> independentServices = [
Provider.value(value: FirebaseNewsService()),
Provider.value(value: FirebaseEventsService())
];
List<SingleChildCloneableWidget> dependentServices = [
ProxyProvider<FirebaseNewsService, NewsListModel>(
builder: (context, newsService, _) {
return NewsListModel(newsService: newsService);
}),
ProxyProvider<FirebaseNewsService, NewsCreateModel>(
builder: (context, newsService, _) {
return NewsCreateModel(newsService: newsService);
},
),
ProxyProvider<FirebaseEventsService, EventsListModel>(
builder: (context, eventsService, _) {
return EventsListModel(eventsService: eventsService);
}),
];
Next is the main.dart file
class MainApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: providers,
child:MaterialApp(
title: 'MyApp',
initialRoute: RoutePaths.Home,
onGenerateRoute: Router.generateRoute, )
);
}
}
Next is the router.dart file where routing happens
class Router {
static Route<dynamic> generateRoute(RouteSettings settings) {
switch (settings.name) {
// this is working
case RoutePaths.Home:
return MaterialPageRoute(builder: (context) {
NewsListModel model = Provider.of(context);
return ChangeNotifierProvider<NewsListModel>.value(
value: model, child: NewsPage());
});
break;
case RoutePaths.Events:
return MaterialPageRoute(builder: (_) {
EventListModel model = Provider.of(context);
return ChangeNotifierProvider<EventListModel>.value(
value: model, child: EventsListPage());
});
break;
My homepage file
class NewsPage extends StatelessWidget {
final String _tab1title = allTranslations.text('newsPage.tabtitleone');
final String _tab2title = allTranslations.text('newsPage.tabtitletwo');
static const _tablength = 2;
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: _tablength,
child: Scaffold(
drawer: Menu(), //Maybe Menu is having a different context
body: NestedScrollView(
...
body: Tabbarview(children: [] . // this works fine
In the problem widget the Events List .dart file
class EventListPage extends statelessWidget {
Widget build(BuildContext context) {
EventListModel model = Provider.of(context);
return Scaffold(drawer: Menu(), appBar: AppBar(), body: ChangeNotifierProvider<EventsListModel>.value(
value: model,
child: Consumer<EventsListModel>(
builder: (context, model, child) => model.busy
? Center(
child: CircularProgressIndicator(),
)
: Column(mainAxisSize: MainAxisSize.max, children: <Widget>[
SmartRefresher(
//key: EventsPageModel.eventsFollowKey,
controller: model.refreshController,
enablePullDown: true,
header: WaterDropMaterialHeader(
backgroundColor: Theme.of(context).primaryColor,
),
enablePullUp: true,
onRefresh: model.onRefresh,
onLoading: model.onLoading,
child: buildchild(model, context)),
]),
),
);
}
I always get could not find ancestor of consumer or
could not find the correct provider .Where I am doing it wrong.
the same thing works for the NewsListModel
Was a case sensitive import bug https://github.com/microsoft/TypeScript/issues/21736 .closing this question