I'm trying to have a navigation bar that can navigate into 3 pages (Home, Profile, and Favorites). I follow a course online, somehow it didn't list the file that normally other people do to navigate (example: List _pages = [Home(),Profile()]).
I try to create the list with all the context inside however I got an error on the 'widget' saying that it can't be access in a initalizer.
Since the code is connected to the Firebase, removing 'user widget.user' will lose all the data of the current user such as the their favorites food.
Is there a way to change the body in the Home.dart so that it can navigate to multiple pages since the code below is only ideal for 2 pages (ItemList & ProfileScreen) to navigates.
Home.Dart
class HomeScreen extends StatefulWidget {
final Function logOut;
final User user;
HomeScreen({#required this.logOut, #required this.user});
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
int currentItem = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
'Order Je!',
style: TextStyle(fontSize: 26, fontWeight: FontWeight.bold),
),
centerTitle: true,
backgroundColor: MainColors.whiteColor,
elevation: 0,
),
body: currentItem == 1
? ProfileScreen(
logout: () {
widget.logOut();
},
)
: ItemList(user: widget.user),
bottomNavigationBar: CustomNavBar(
currentSelectedItem: (value) {
setState(() {
currentItem = value;
});
},
),
);
}
}
Custom Nav
class CustomNavBar extends StatefulWidget {
final Function(int) currentSelectedItem;
CustomNavBar({#required this.currentSelectedItem});
#override
_CustomNavBarState createState() => _CustomNavBarState();
}
class _CustomNavBarState extends State<CustomNavBar> {
int selectedItem = 0;
#override
Widget build(BuildContext context) {
return CurvedNavigationBar(
color: Colors.white,
height: 52,
backgroundColor: MainColors.primaryColor,
animationCurve: Curves.easeInOut,
animationDuration: Duration(milliseconds: 350),
index: selectedItem,
onTap: (currItem) {
setState(() {
selectedItem = currItem;
widget.currentSelectedItem(currItem);
});
},
items: <Widget>[
Icon(Icons.home),
Icon(Icons.account_circle),
Icon(Icons.favorite)
],
);
}
}
Sorry for asking such stupid question. I'm new in Flutter and Firebase development.
Thank You.
Looking at you problem, I would suggest you to not split the Home and the navigation bar components, Kindly take a look into this, it will help you
Google Flutter Bottom Navigation Bar
We can use widget keyword when we want to refer to the method defined in the stateful class not in the state.
To solve this:
You can pass user and logout method to the state of the class.
#override
_HomeScreenState createState() => _HomeScreenState(logOut,user);
and in the state create a constructor
final Function logOut;
final User user;
_HomeScreenState(this.logOut,this.user);
Hope this will help you and can you please upload the error that it is giving.
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);
}
}
While working with firebase authentication weird error without any notes happen to me.
This time application stops after I press set name button. Instantly in VScode I am redirected to this page:
As I said there is no error in debug console, no notes. No expections to see.
I guess there is something wrong with setting displayName but not clearly what.
This is full code of the class:
class Verified extends StatefulWidget {
#override
_VerifiedState createState() => _VerifiedState();
}
class _VerifiedState extends State<Verified> {
final formKey = GlobalKey<FormState>();
final nameController = TextEditingController();
final _auth = FirebaseAuth.instance;
validate(displayName) {
if (formKey.currentState.validate()) {
setName(displayName);
}
}
setName(displayName) async {
try {
await _auth.currentUser.updateProfile(displayName: displayName);
} catch (e) {
log(e.code.toString());
}
log(_auth.currentUser.displayName.toString());
}
#override
Widget build(BuildContext context) {
return Material(
child: Padding(
padding: const EdgeInsets.all(100.0),
child: Column(
children: [
Text('choose your username'),
Form(
key: formKey,
child: TextFormField(
controller: nameController,
decoration: InputDecoration(hintText: 'name'),
),
),
RaisedButton(
child: Text('set name'),
onPressed: () => validate(nameController))
],
),
),
);
}
}
Thank you in advance
SOLUTION
When I remove from function actions with _auth.currentUser everything works, I also moved this function to the place where the user was logged in/registered and it also worked.
So as I think the error was because firebase saw no user and the solution is to use .currentUser in the same class/function as registering/logging in or saving user after those actions.
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:
I have a snippet of code which I copied from Firestore example:
Widget _buildBody(BuildContext context) {
return new StreamBuilder(
stream: _getEventStream(),
builder: (context, snapshot) {
if (!snapshot.hasData) return new Text('Loading...');
return new ListView(
children: snapshot.data.documents.map((document) {
return new ListTile(
title: new Text(document['name']),
subtitle: new Text("Class"),
);
}).toList(),
);
},
);
}
But I get this error
type 'List<dynamic>' is not a subtype of type 'List<Widget>'
What goes wrong here?
The problem here is that type inference fails in an unexpected way. The solution is to provide a type argument to the map method.
snapshot.data.documents.map<Widget>((document) {
return new ListTile(
title: new Text(document['name']),
subtitle: new Text("Class"),
);
}).toList()
The more complicated answer is that while the type of children is List<Widget>, that information doesn't flow back towards the map invocation. This might be because map is followed by toList and because there is no way to type annotate the return of a closure.
I had a list of strings in firestore that I was trying to read in my app. I got the same error when I tried to cast it to List of String.
type 'List<dynamic>' is not a subtype of type 'List<Widget>'
This solution helped me. Check it out.
var array = document['array']; // array is now List<dynamic>
List<String> strings = List<String>.from(array);
You can Cast dynamic List to List With specific Type:
List<'YourModel'>.from(_list.where((i) => i.flag == true));
I have solve my problem by converting Map to Widget
children: snapshot.map<Widget>((data) =>
_buildListItem(context, data)).toList(),
I think that you use _buildBody in the children properties of some widget, so children expect a List Widget (array of Widget) and _buildBody returns a 'List dynamic'.
In a very simple way, you can use an variable to return it:
// you can build your List of Widget's like you need
List<Widget> widgets = [
Text('Line 1'),
Text('Line 2'),
Text('Line 3'),
];
// you can use it like this
Column(
children: widgets
)
Example (flutter create test1 ; cd test1 ; edit lib/main.dart ; flutter run):
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
List<Widget> widgets = [
Text('Line 1'),
Text('Line 2'),
Text('Line 3'),
];
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text("List of Widgets Example")),
body: Column(
children: widgets
)
)
);
}
}
Another example using a Widget (oneWidget) within a List of Widgets(arrayOfWidgets). I show how extents a widget (MyButton) to personalize a widget and reduce the size of code:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
List<Widget> arrayOfWidgets = [
Text('My Buttons'),
MyButton('Button 1'),
MyButton('Button 2'),
MyButton('Button 3'),
];
Widget oneWidget(List<Widget> _lw) { return Column(children: _lw); }
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text("Widget with a List of Widget's Example")),
body: oneWidget(arrayOfWidgets)
)
);
}
}
class MyButton extends StatelessWidget {
final String text;
MyButton(this.text);
#override
Widget build(BuildContext context) {
return FlatButton(
color: Colors.red,
child: Text(text),
onPressed: (){print("Pressed button '$text'.");},
);
}
}
I made a complete example that I use dynamic widgets to show and hide widgets on screen, you can see it running online on dart fiddle, too.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
List item = [
{"title": "Button One", "color": 50},
{"title": "Button Two", "color": 100},
{"title": "Button Three", "color": 200},
{"title": "No show", "color": 0, "hide": '1'},
];
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text("Dynamic Widget - List<Widget>"),backgroundColor: Colors.blue),
body: Column(
children: <Widget>[
Center(child: buttonBar()),
Text('Click the buttons to hide it'),
]
)
)
);
}
Widget buttonBar() {
return Column(
children: item.where((e) => e['hide'] != '1').map<Widget>((document) {
return new FlatButton(
child: new Text(document['title']),
color: Color.fromARGB(document['color'], 0, 100, 0),
onPressed: () {
setState(() {
print("click on ${document['title']} lets hide it");
final tile = item.firstWhere((e) => e['title'] == document['title']);
tile['hide'] = '1';
});
},
);
}
).toList());
}
}
Maybe it helps someone. If it was is useful to you, let me know clicking in up arrow, please. Thanks.
https://dartpad.dev/b37b08cc25e0ccdba680090e9ef4b3c1
My Solution is, you can cast List<dynamic> into List<Widget>, you can add a simple code after snapshot.data.documents.map into snapshot.data.documents.map<Widget>, like the code i will show you below
From this
return new ListView(
children: snapshot.data.documents.map((document) {
return new ListTile(
title: new Text(document['name']),
subtitle: new Text("Class"),
);
}).toList(),
);
Into this
return new ListView(
children: snapshot.data.documents.map<Widget>((document) {
return new ListTile(
title: new Text(document['name']),
subtitle: new Text("Class"),
);
}).toList(),
);
This works for meList<'YourModel'>.from(_list.where((i) => i.flag == true));
Example:
List<dynamic> listOne = ['111','222']
List<String> ListTwo = listOne.cast<String>();
I think , after changing the type of List from dynamic to String & doing hot reload will give this error , hot restart is the solution..
Remember : hot restart only rebuild the build() function , not the whole class & declaring the List at the top of class is outside of build() function
changing to the list by adding .toList() resolved the issue
Changing
List list = [];
to this:
List<Widget> list = [];
Solved My Problem!!