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.
Related
So, I am building an app with 3 screens so far: Login, Register and Home. Login and Register pages work just fine. I managed to use a Provider to listen to the user authentication state, and direct him either to the Login or Home page, depending on whether he is logged in or not.
Now for the Home Page: I basically want it to show a list of stores I have in my Firestore Database. To do this, I am wrapping the Scaffold with a StreamProvider<List>.value
But I I keep getting the following error message:
Error: Could not find the correct Provider<List> above this Home Widget
Now, if I understand this correctly, this is because the Provider for the stores is not declared in the main file, like I did with the Provider for the user authentication.
Is there any way of having the Provider for the stores declared just in this Home page, and not in the main file, since I do not need to access the database in the other pages?
main function:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MaterialApp(home: CondoApp()));
}
class CondoApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return StreamProvider<User>.value(
value: AuthService().user,
child: MaterialApp(
theme: MyThemes(context).mainTheme,
home: Wrapper(),
routes: myRoutes,
),
);
}
}
wrapper (decides whether user is logged in or not, and then show the correct page)
class Wrapper extends StatelessWidget {
#override
Widget build(BuildContext context) {
final user = Provider.of<User>(context);
// return either StarPage or Home
if (user == null) {
return StartPage();
} else {
return Home();
}
}
}
home page
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
#override
Widget build(BuildContext context) {
final stores = Provider.of<List<Store>>(context);
return StreamProvider<List<Store>>.value(
value: DatabaseService().stores,
child: Scaffold(
appBar: AppBar(
title: Text('Title'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text(stores[0].name),
Text(stores[0].image),
],
),
),
),
);
}
}
database file
class DatabaseService {
final String uid;
DatabaseService({ this.uid });
// Collection reference
final CollectionReference storeCollection = FirebaseFirestore.instance.collection('stores');
// Make a store list from snapshot object
List<Store> _storeListfromSnapshot(QuerySnapshot snapshot) {
return snapshot.docs.map((doc){
return Store(
name: doc.data()['name'] ?? '',
image: doc.data()['image'] ?? ''
);
}).toList();
}
// Get stores stream
Stream<List<Store>> get stores {
return storeCollection.snapshots().map(_storeListfromSnapshot);
}
}
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.
Here, i am trying to implement Firebase login and signup system. i am trying to change screen base on user login or not.
Basically, i want to show feed screen when user is login and when user is not login i want to show login scree. if i do login in login screen it is working fine, so i did not added that code here. but issue come when i navigate from login screen to sign up scree and even if i successfully sign up it is not showing me feed screen. When i hot reload it show me feed screen.
Moreover, i also make sure that it is reaching where i am changing screen by print in console.
Note: i know i can using function to change between login screen and signup screen, so i don't need Navigator, which will again work for me. but i want to know why after navigating using navigator it is not working.
class DeleteWidget extends StatefulWidget {
#override
_DeleteWidgetState createState() => _DeleteWidgetState();
}
class _DeleteWidgetState extends State<DeleteWidget> {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: StreamBuilder<FirebaseUser>(
stream: FirebaseAuth.instance.onAuthStateChanged,
builder: (BuildContext context, snapshot) {
print(snapshot.hasData);
print(snapshot.connectionState);
if (ConnectionState.active == snapshot.connectionState) {
print("object 1");
if (snapshot.hasData) {
print("object 2");
return Feed();
} else {
print("object 3");
return LoginScreen();
}
} else {
return LoginScreen();
}
}),
);
}
}
class LoginScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Container(
child: RaisedButton(
child: Text("login"),
onPressed: () async {
Navigator.push(
context, MaterialPageRoute(builder: (context) => SignUp()));
},
),
),
),
);
}
}
class SignUp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Container(
child: RaisedButton(
child: Text("Sign up"),
onPressed: () async {
await FirebaseAuth.instance.signInAnonymously();
},
),
),
),
);
}
}
class Feed extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Container(
child: RaisedButton(
child: Text("feed"),
onPressed: () async {
await FirebaseAuth.instance.signOut();
},
),
),
),
);
}
}
You can use the Provider Package to Listen if user is logged in and use a Wrapper to direct the user to the correct screen. If the user logs out at any stage, they will be automatically redirected to the Login Screen.
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
User _userFromFirebaseUser(FirebaseUser user) {
return user != null ? User(uid: user.uid) : null;
}
#override
Widget build(BuildContext context) {
return StreamProvider<User>.value(
value: FirebaseAuth.instance.onAuthStateChanged.map(_userFromFirebaseUser),
child: MaterialApp(
home: Wrapper(),
),
);
}
}
Wrapper
class Wrapper extends StatelessWidget {
#override
Widget build(BuildContext context) {
final user = Provider.of<User>(context);
if(user == null) {
return LoginScreen();
} else {
return Feed();
}
}
}
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().
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: