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);
}
}
Related
I have specific firestore document and I want get the value of field of that document updated every second because the field represent the number of notifications of user, But when I Looking for stream provider first time I was read we can't use it inside Stateful widget.
My Document Path
My home page code:
class Home extends StatefulWidget {
final User me;
Home({
Key key,
this.me,
}) : super(key: key);
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
final Firestore _firestore = Firestore.instance;
int numberOfNotifications;
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: GlobalUniversal.whiteBG,
body: Center(
child: Text(numberOfNotifications.toString()),
),
);
}
}
db.dart
class DatabaseService {
final Firestore db = Firestore.instance;
Future<UserNotification> getUserNotification(String doc) async {
var snap = await db.collection(NOTIFICATIONS_USERS_ONE_COLLECTION).document(doc).get();
return UserNotification.fromMap(snap.data);
}
Stream<UserNotification> streamUserNotifications(String doc){
return db.collection(NOTIFICATIONS_USERS_ONE_COLLECTION).document(doc).snapshots().map((snap) => UserNotification.fromMap(snap.data));
}
}
user.dart
class UserNotification {
int userNotifications;
UserNotification({this.userNotifications});
factory UserNotification.fromMap(Map data) {
return UserNotification(
userNotifications: data[UN_READ_COUNTS_FIELD] ?? 0,
);
}
}
but when I try call provider inside home page I got an error.
error: The named parameter 'stream' isn't defined. (undefined_named_parameter)
Don't use the .value if you are instantiating your stream in your provider
StreamProvider<UserNofitication>(
create: (BuildContext context) => db.streamUserNotification(widget.me.doc),
child: Center(
child: Text(numberOfNotifications.toString()),
),
),
If db.streamUserNotification(widget.me.doc) is already an instance of stream, you can use StreamProvider.value and the named parameter is value and not stream :
StreamProvider<UserNofitication>.value(
value: db.streamUserNotification(widget.me.doc),
child: Center(
child: Text(numberOfNotifications.toString()),
),
),
EDIT
to use the stream you can use the builder of the StreamProvider to read the context
StreamProvider<UserNofitication>(
create: (BuildContext context) => db.streamUserNotification(widget.me.doc),
builder: (BuildContext context, Widget child) {
numberOfNotifications = context.watch<UserNotification>()?.userNotifications ?? 0;
return Center(
child: Text(numberOfNotifications.toString()),
);
},
),
Intended Flow: Only show the home page when the user verifies their email.
What is actually happening: The user can pop the navigation stack to show the home page.
I'm using Firebase to handle my authentication flow and currently am using a StreamBuilder to show either the landing page or the home page at the root of the app depending on if a user is verified and not null.
//create user object based on Firebase User
UserApp _userFromFirebaseUser(User user) {
return user != null ? UserApp(uid: user.uid, isVerified: user.emailVerified):null;
}
//userapp type class
class UserApp {
final String uid;
final bool isVerified;
UserApp({this.isVerified, this.uid});
}
Then I have a StreamBuilder that lives on top of my main.dart file:
class AuthWidgetBuilder extends StatelessWidget {
const AuthWidgetBuilder({Key key, #required this.builder}) : super(key: key);
final Widget Function(BuildContext, AsyncSnapshot<UserApp>) builder;
#override
Widget build(BuildContext context) {
final authService = Provider.of<AuthService>(context, listen: false);
return StreamBuilder<UserApp>(
stream: authService.onAuthStateChanged,
builder: (context, snapshot) {
final UserApp user = snapshot.data;
//only show home page if user is non null and also if they verified their email.
if (user != null && authService.currentUser().isVerified == true) {
return MultiProvider(
providers: [
Provider<UserApp>.value(value: user),
],
child: builder(context, snapshot),
);
}
return builder(context, snapshot);
});
}
}
class AuthWidget extends StatelessWidget {
const AuthWidget({Key key, #required this.userSnapshot}) : super(key: key);
final AsyncSnapshot<UserApp> userSnapshot;
#override
Widget build(BuildContext context) {
if (userSnapshot.connectionState == ConnectionState.active) {
return userSnapshot.hasData ? HomePage() : LandingPage();
}
return Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
}
}
Then this is my main.dart file with the StreamBuilder and AuthService class on top of the widget tree:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Provider<AuthService>(
create: (_) => AuthService(),
child: AuthWidgetBuilder(builder: (context, userSnapshot) {
return MaterialApp(
home: AuthWidget(userSnapshot: userSnapshot),
);
}));
}
How come I can pop the navigation stack to reveal the home page, even when I have not yet verified the user through email? What do I need to change in my code to make sure that the user can only see the home page, after verifying their email? Is there any resource I can look at to understand these concepts better?
Assumptions/what you want to achieve
Currently, I am learning about application development using Flutter+Firebase.
I want to implement the following functions as a part of the login function.
Transition to the login page and login only when logging in for the first time
Unless you log out, opening the app after exiting the app does not bring you to the login page
I did some research on my own and tried them, but they didn't work.
As a concrete method, how can the above functions be implemented?
Also, I am currently implementing a function that uses Firebase's Email and Password.
Is it a common method to implement the above functions even for Google login and Facebook login?
Since I am a new student of Flutter, I have many things that I do not understand, but I hope you can teach me.
Currently implemented
Login function using Firebase Auth
Database using Firebase Cloud Store
What I tried
Check the login status using Current User of Firebase Auth
Additional information (FW/tool version, etc.)
Used
FirebaseAuth
FirebaseCloudStore
class MyApp extends StatelessWidget {
// This widget is the root of your application.
final UserState user = UserState();
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider<UserState>.value(
value: user,
child: MaterialApp(
// Hide debug label
debugShowCheckedModeBanner: false,
title:'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: LoginCheck(),
initialRoute: "/",
routes:<String, WidgetBuilder>{
// "/": (BuildContext context) => LoginPage(),
AddRecord.routeName: (BuildContext context) => AddRecord(),
"/login":(BuildContext context) => LoginPage(),
"/home":(BuildContext context) => PageManager()
},
)
);
}
}
class LoginCheck extends StatefulWidget{
LoginCheck({Key key}): super(key: key);
#override
_LoginCheckState createState() => _LoginCheckState();
}
class _LoginCheckState extends State<LoginCheck>{
#override
void initState(){
super.initState();
checkUser();
// TODO: implement initState
}
void checkUser() async{
final UserState userState = Provider.of<UserState>(context);
final currentUser = await FirebaseAuth.instance.currentUser();
print(currentUser);
if(currentUser == null){
Navigator.pushReplacementNamed(context,"/login");
}else{
userState.setUser(currentUser);
Navigator.pushReplacementNamed(context, "/home");
}
}
#override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
body: Center(
child: Container(
child: Text("Loading..."),
),
),
);
}
}
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 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: