I want to have data from firebase in realtime on a widget. When I try to use a StreamProvider and then use Navigator.push(), the pushed widget can't get the value with Provider.of(context).
I tried putting the StreamProvider as the parent of MaterialApp. This works but the user needs to be logged in order for the Stream to get the data of the user.
I also tried using a ScopedModel. This works as well, but I don't know if this is the best approach to do this.
I would like to avoid using a global StreamProvider and would like to have an efficient solution (as little reads from firebase as possible)
main.dart
void main() => runApp(MyApp());
final GlobalKey<ScaffoldState> mainScaffoldKey = GlobalKey<ScaffoldState>();
final GlobalKey<ScaffoldState> authScaffoldKey = GlobalKey<ScaffoldState>();
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ScopedModel<ScreenModel>(
model: ScreenModel(),
child: MultiProvider(
providers: [
StreamProvider<User>.value(value: authService.userDoc,),
StreamProvider<bool>.value(value: authService.loading.asBroadcastStream())
],
child: MaterialApp(
title: "ListAssist",
theme: ThemeData(
primarySwatch: Colors.indigo,
),
home: MainApp()
),
)
);
}
}
class MainApp extends StatefulWidget {
#override
_MainAppState createState() => _MainAppState();
}
class _MainAppState extends State<MainApp> {
#override
Widget build(BuildContext context) {
User user = Provider.of<User>(context);
bool loading = Provider.of<bool>(context);
return AnimatedSwitcher(
duration: Duration(milliseconds: 600),
child: user != null ?
StreamProvider<Group>.value(
value: databaseService.streamGroupsFromUser(),
child: Scaffold(
key: mainScaffoldKey,
body: Body(),
drawer: Sidebar(),
),
) : Scaffold(
key: authScaffoldKey,
body: AnimatedSwitcher(
duration: Duration(milliseconds: 600),
child: loading ? SpinKitDoubleBounce(color: Colors.blueAccent) : AuthenticationPage(),
),
resizeToAvoidBottomInset: false,
)
);
}
}
class Body extends StatefulWidget {
createState() => _Body();
}
class _Body extends State<Body> {
#override
Widget build(BuildContext context) {
return ScopedModelDescendant<ScreenModel>(
builder: (context, child, model) => model.screen
);
}
}
In the Sidebar I can change to GroupView and the Provider still works.
sidebar.dart (important part)
onTap: () {
ScreenModel.of(context).setScreen(GroupView(), "Gruppen");
Navigator.pop(context);
},
The GroupView has GroupItem in it
group-item.dart (important part)
onTap: () => Navigator.push(
context,
MaterialPageRoute(builder: (context) {
return GroupDetail();
}),
)
When I try to use Group group = Provider.of<Group>(context); in GroupDetail or a child widget of it, it says that it cannot find any Provider for the context.
Here is the repository.
I figured out how to do it. I used a package called custom_navigator.
In sidebar.dart I changed the child when someone changes to the group view to the following:
StreamProvider<Group>.value(
value: databaseService.streamGroupsFromUser(user.uid),
child: CustomNavigator(
home: GroupView(),
pageRoute: PageRoutes.materialPageRoute,
)
)
With the CustomNavigator I can still use Provider.of<Group>(context) to get the data, even after a Navigator.push().
Related
We have a cover page that has two buttons. One leads to the Contractor's login and registration page, and the other button leads to Hirer login and Registration.
In order to check whether a user is already logged into the app, we are using a wrapper.dart which contains the following code:
#override
Widget build(BuildContext context) {
final user = Provider.of<User>(context);
print(user);
enter code here
if (user == null) {
return Authenticate();
} else {
return Home();
}
}
}
This is our code snippet for contractorwrapper. We have a similar one for hirerWrapper as well. We are getting this error:
/flutter (23750): ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
I/flutter (23750): The following ProviderNotFoundException was thrown building ContractorWrapper(dirty):
I/flutter (23750): Error: Could not find the correct Provider above this ContractorWrapper Widget
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(
create: (context) => User())],
child: MaterialApp(
home: Scaffold(
appBar: AppBar(
backgroundColor: Colors.blue,
title: Text("First appbar"),
),
body: Text("content")),
),
);
}
}
Assuming that the class User extends the class ChangeNotifier you have to add a ChangeNotifierProvider above your MaterialApp, which defines how the User is created.
Here you can find a great guide how to integrate the Provider into your app.
This would look like the following:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => User(),
child: MaterialApp(
home: Scaffold(
appBar: AppBar(
backgroundColor: Colors.blue,
title: Text("First appbar"),
),
body: Text("content")),
),
);
}
}
//just an example
class User extends ChangeNotifier {
String _name;
set name(String value) {
_name = value;
notifyListeners();
}
String get name => _name;
}
If something is unclear - Feel free to ask.
I have two streams whose data I need to use app-wise.
My main obstacle is that one of the streams needs the other's data, thus, I cannot call a MultiProvider.
My current implementation looks as follows, however I do not like it: I think it is not ok to return multiple MaterialApps. Actually, my app turns black for a while, when changing from one MaterialApp to the other.
This is my current implementation:
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return StreamProvider<User>.value( //First, listen to the User Stream here
value: AuthService().user,
child: MyMaterialApp(),
);
}
}
class MyMaterialApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
final user = Provider.of<User>(context); //To get the user data here, and use it bellow
if (user == null){ //If I don't have the User yet, return Loading()
return MaterialApp(
debugShowCheckedModeBanner: false,
title: "myApp",
theme: myTheme(),
home: Loading(),
);
} else {
return StreamProvider<UserData>.value(
value: DatabaseService(uid: user.uid).userData, //Once I have it, use it to build the UserData Stream
child: MaterialApp(
debugShowCheckedModeBanner: false,
title: "myApp",
theme: myTheme(),
home: Wrapper(),
initialRoute: '/',
routes: {
'/home': (context) => Wrapper(),
//...
}
),
);
}
}
}
Thank you very much!
Based on this post https://github.com/rrousselGit/provider/issues/222 I was able to solve it by doing the following:
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
StreamProvider<User>.value(value: AuthService().user),
Consumer<User>(
builder: (context, user, child) => StreamProvider<UserData>.value(
value: DatabaseService(uid: user == null ? null : user.uid).userData,
child: child,
),
)
],
child: MaterialApp(
debugShowCheckedModeBanner: false,
title: "myApp",
theme: myTheme(),
home: Wrapper(),
initialRoute: '/',
routes: {
'/home': (context) => Wrapper(),
//...
}
),
);
}
}
The Consumer listens to the User data and passes it to the next Stream.
I'm currently learning Flutter and I wanted to try out Network Requests and working with Futures.
I want to show a random image from unsplash.com using their API and I want to change the image every time I press a certain button.
I tried implementing a function to change the image, but it doesn't work.
My code looks like this:
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:async';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: RandomImage(),
);
}
}
class RandomImage extends StatefulWidget {
#override
_RandomImageState createState() => _RandomImageState();
}
class _RandomImageState extends State<RandomImage> {
static String imageUrl = 'https://source.unsplash.com/random/300x200';
Future _imgFuture = http.get(imageUrl);
void _changeImage() async {
_imgFuture = http.put(imageUrl);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: (Text('Hello')),
),
body: Center(
child: Column(
children: [
Spacer(),
FutureBuilder(
future: _imgFuture,
builder: (context, snapshot) {
if (snapshot.hasError) {
return Text('Oops, there was an error');
} else if (snapshot.hasData) {
return Image.network(imageUrl);
} else {
return Text('No value yet');
}
},
),
RaisedButton(
child: Text('Change Image!'),
onPressed: () => setState(() {
_changeImage();
}),
),
Spacer(),
],
),
),
);
}
}
Actually, Image.network is keeping your image, for more detail to see It here. The solution for this issue is make a simple useless query to api, so the image will be identical differently in flutter.
RaisedButton(
child: Text('Change Image!'),
onPressed: () => setState(() {
// _changeImage();
imageUrl="https://source.unsplash.com/random/300x200?v=${DateTime.now().millisecondsSinceEpoch}";
}),
),
The image won't change If you call the api too frequently, you might want to add a timer to prevent user from clicking too often.
I think the problem is in the _changeImage() method, try replace http.put with http.get.
I need to load the saved themes from shared preference async from app start. After it loaded, replace the placeholder two themes. But below code failed because even it loaded the results for several themes, the app always displays the two themes.
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return MyAppState();
}
}
class MyAppState extends State<MyApp> {
final bloc = AppThemeBloc();
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
bloc.getAppThemes();
return buildApp();
}
Widget buildApp() {
return StreamBuilder<List<AppTheme>>(
stream: bloc.subject.stream,
builder: (context, AsyncSnapshot<List<AppTheme>> snapshot) {
if (snapshot.hasData) {
return ThemeProvider(
saveThemesOnChange: true,
loadThemeOnInit: true,
themes: snapshot.data,
child: MaterialApp(
home: ThemeConsumer(
child: HomePage(),
),
),
);
} else {
return ThemeProvider(
saveThemesOnChange: true,
loadThemeOnInit: true,
themes: [
AppTheme.light(),
AppTheme.dark(),
],
child: MaterialApp(
home: ThemeConsumer(
child: HomePage(),
),
),
);
}
});
}
}
After the data is received and the streambuilder is refreshed which updates the state so build function is being executed again thus calling:
bloc.getAppThemes();
again which waits for any data to pass in the stream returning your placeholder themes, place bloc.getAppThemes function in the initState function:
#override
void initState() {
super.initState();
bloc.getAppThemes();
}
I am using Redux within Flutter (and I am just starting to learn both). I have been trying to figure out how to switch between the pages of a PageView using the PageView's PageController.
However, whenever I try to use the PageController.jumpToPage() function, I get an exception stating:
"The following assertion was thrown while finalizing the widget tree: setState() or markNeedsBuild() called when widget tree was locked."
When I attempt to call the PageController.jumpToPage() in my reducer, it does navigate to the page within the pageview; but the exception gets thrown.
I have also tried just building a new PageController in the reducer, and just setting the PageController's initial page property to the desired page, but that didn't seem to do anything.
I have run out of ideas on how to figure this out on my own, so I thought I would ask here. Any help would be appreciated.
I have thrown together a quick sample showing what I am trying to do:
import 'package:flutter/material.dart';
import 'package:redux/redux.dart';
import 'package:flutter_redux/flutter_redux.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
final store = Store<AppState>(appReducer,
initialState: AppState.initial(), middleware: []);
#override
Widget build(BuildContext context) {
return StoreProvider(
store: store,
child: MaterialApp(
title: 'PageView Example With Redux',
home: MyPageViewContainer(),
),
);
}
}
class AppState {
final List<Widget> pageViewList;
final PageController pageController;
AppState({
this.pageViewList,
this.pageController,
});
factory AppState.initial() {
return AppState(
pageViewList: [
PageOne(),
PageTwo(),
],
pageController: PageController(initialPage: 0),
);
}
AppState copyWith({
List<Widget> pageViewList,
PageController pageController,
}) {
return AppState(
pageViewList: pageViewList ?? this.pageViewList,
pageController: pageController ?? this.pageController,
);
}
}
AppState appReducer(AppState state, action) {
if (action is NavigateToPageOneAction) {
state.pageController.jumpToPage(0);
return state;
}
else if (action is NavigateToPageTwoAction) {
state.pageController.jumpToPage(1);
return state;
}
else {
return state;
}
}
class NavigateToPageOneAction {}
class NavigateToPageTwoAction {}
class MyPageView extends StatelessWidget {
final List<Widget> pageViewList;
final PageController pageController;
final Function onPageChanged;
MyPageView({
this.pageViewList,
this.pageController,
this.onPageChanged,
});
#override
Widget build(BuildContext context) {
return PageView(
controller: pageController,
children: pageViewList,
onPageChanged: onPageChanged,
);
}
}
class MyPageViewContainer extends StatelessWidget {
MyPageViewContainer({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return StoreConnector<AppState, _MyPageViewModel>(
converter: (Store<AppState> store) => _MyPageViewModel.create(store),
builder: (BuildContext context, _MyPageViewModel vm) {
return MyPageView(
pageViewList: vm.pageViewList,
pageController: vm.pageController,
);
},
);
}
}
class _MyPageViewModel {
final List<Widget> pageViewList;
final PageController pageController;
final Function onPageChanged;
_MyPageViewModel({
this.pageViewList,
this.pageController,
this.onPageChanged,
});
factory _MyPageViewModel.create(Store<AppState> store) {
_onPageChanged() {}
return _MyPageViewModel(
pageViewList: store.state.pageViewList,
pageController: store.state.pageController,
onPageChanged: _onPageChanged(),
);
}
}
class PageOne extends StatelessWidget {
PageOne();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Page One"),
),
backgroundColor: Colors.black,
body: Column(),
drawer: MyDrawer(),
);
}
}
class PageTwo extends StatelessWidget {
PageTwo();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Page Two"),
),
backgroundColor: Colors.blue,
body: Column(),
drawer: MyDrawer(),
);
}
}
class MyDrawer extends StatelessWidget {
MyDrawer({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return StoreConnector<AppState, _MyDrawerViewModel>(
converter: (Store<AppState> store) => _MyDrawerViewModel.create(store),
builder: (BuildContext context, _MyDrawerViewModel vm) {
return Drawer(
child: ListView(
children: <Widget>[
Container(
child: ListTile(
title: Text(vm.pageOneText),
onTap: vm.pageOneOnTap,
),
),
Container(
child: ListTile(
title: Text(vm.pageTwoText),
onTap: vm.pageTwoOnTap,
),
),
],
),
);
},
);
}
}
class _MyDrawerViewModel {
final String pageOneText;
final String pageTwoText;
final Function pageOneOnTap;
final Function pageTwoOnTap;
_MyDrawerViewModel({
this.pageOneText,
this.pageTwoText,
this.pageOneOnTap,
this.pageTwoOnTap,
});
factory _MyDrawerViewModel.create(Store<AppState> store) {
_goToPageOne() {
store.dispatch(NavigateToPageOneAction());
}
_goToPageTwo() {
store.dispatch(NavigateToPageTwoAction());
}
return _MyDrawerViewModel(
pageOneText: "Page One",
pageTwoText: "Page Two",
pageOneOnTap: _goToPageOne,
pageTwoOnTap: _goToPageTwo,
);
}
}
I seem to have figured out how to solve my problem. I saw an answer in this post: Flutter: setState() or markNeedsBuild() called when widget tree was locked... during orientation change
In that post the OP was encountering the same error when changing between portrait and landscape mode while the Drawer was open. The answer in that post suggested calling Navigator.pop() (which closes the Drawer) before changing view modes.
So I gave that a try and closed my Drawer using the Navigator.pop() prior to using the PageController's .jumpToPage method. This seems to work, and allows me to navigate between pages of the PageView using onTap events from the Drawer, without throwing the "The following assertion was thrown while finalizing the widget tree: setState() or markNeedsBuild() called when widget tree was locked" exception.
I assume that this means that while the Drawer is open, the widget tree is placed into a locked state.
Hopefully this helps someone, as it took me a while to figure out.
#Blau
Sometimes an event happened outside any widgets you built. e.g. (1) A timer that increment a 'Global Counter', this counter will be shown in many pages/widgets (2) A message sent from the socket server, on receiving this message/event, the user may be anywhere(any pages/widgets), and you don't know where to 'setState' (Or the widget is actually not there because the user is not at that page)
I've built 2 examples that demonstrate how to use Redux to solve this kind of problems:
Example 1: (Use a multi-thread timer to 'setState' a widget when an external event fires)
https://github.com/lhcdims/statemanagement01
Example 2: (Use Redux to refresh a widget when an external event fires)
https://github.com/lhcdims/statemanagement02
Demo Screen Shot: