I am trying to hide a Container in my app when a user logs in, but my UI won't change even if I use onAuthStateChanged or setState()
I am using a StreamBuilder in my main.dart and I am using a dependency that is similar to a Hamburger Menu (https://pub.dev/packages/kf_drawer) that passes a variable which decides the Visibility of my Container
return MaterialApp(
home: StreamBuilder(
stream: FirebaseAuth.instance.onAuthStateChanged,
builder: (_, snap) {
if (snap.connectionState == ConnectionState.active) {
if (snap.data != null) {
return DrawerMenu(firebaseUser: snap.data, visibleLogin: false);
} else {
return DrawerMenu(firebaseUser: null, visibleLogin: true);
}
} else {
return CircularProgressIndicator();
}
},
),
);
This is my code where the firebase user first passes to the DrawerMenu() and then to my Home(),
KFDrawerItem.initWithPage(
text: Text(
'Home',
style: TextStyle(color: Colors.white),
),
icon: Icon(Icons.settings, color: Colors.white),
page: Home(
firebaseUser: widget.firebaseUser,
visibleLogin: widget.visibleLogin,
),
),
This is the code for my Container, however my UI won't update as soon as my user logs in but only when I navigate through the other pages in my app, nor does the state of the app get saved when I re-open my app after quitting it.
//Simplified code for example purposes
Widget login(bool visibleLogin) {
return Visibility(
visible: visibleLogin,
child: Align(
alignment: Alignment.bottomCenter,
child: Container(),
),
);
}
I want to achieve something like this, the bottom login Container disappearing as soon as the user logs in.
BEFORE LOGGING IN - https://i.stack.imgur.com/BKfby.png
AFTER LOGGING IN - https://i.stack.imgur.com/5zMHB.png
Something might not be right with your stream, are you sure you are getting stream reacting to change if a stream is fine, you can then look to your hamburger it might somehow not working propper.
create a StreamSubscription from FirebaseAuth.instance.onAuthStateChanged in the initState() function inside the DrawerMenu
to setState((){}) of the DrawerMenu screen depending on the user state;
and remove the StreamBuilder from the MaterialApp
Related
Because the code is to big I will try and sum it up in words.
This is the latest exception:
Error: Could not find the correct Provider<ProdEntriesSearchCubit> above this BlocListener<ProdEntriesSearchCubit, ProdEntriesSearchState> Widget
This likely happens because you used a `BuildContext` that does not include the provider
of your choice. There are a few common scenarios:
- The provider you are trying to read is in a different route.
Providers are "scoped". So if you insert of provider inside a route, then
other routes will not be able to access that provider.
- You used a `BuildContext` that is an ancestor of the provider you are trying to read.
Make sure that BlocListener<ProdEntriesSearchCubit, ProdEntriesSearchState> is under your MultiProvider/Provider<ProdEntriesSearchCubit>.
This usually happens when you are creating a provider and trying to read it immediately.
In screen 1 I have the following build method:
Widget build(BuildContext context) {
final entriesState = context.watch<ProdEntriesCubit>().state;
return BlocProvider(
create: (context) => ProdEntriesSearchCubit(
productsRepository: context.watch<ProductsRepository>(),
),
child: Builder(
builder: (context) => SafeScreen(
child: Scaffold(
body: _buildBody(context, entriesState: entriesState),
floatingActionButton: _buildFab(context),
),
),
),
);
}
_buildFab(BuildContext context) {
return FloatingActionButton(
child: Icon(
Icons.add,
color: Colors.white,
),
onPressed: () async {
await navigatorPush(context, screen: AdminProdEntryScreen());
},
);
}
In AdminProdEntryScreen I do again:
navigatorPush(
context,
screen: EntryProdSearchScreen(),
);
In EntryProdSearchScreen I get the error from above.
Why is the BloC/Cubit not found in the widget tree?
I even used multiple Builder widgets but I am always hit by this exception.
When you provide your BLoC, it has access to the current widget tree, when you navigate to another screen it won't have access to that BLoC.
You can solve this in one of two ways.
1
You wrap your whole app with a bloc (Multi) provider that you can access the bloc no matter the navigation.
The reason that this works is because you are wrapping your navigation within MaterialApp with the bloc provider.
runApp(
MultiBlocProvider(
providers: [
BlocProvider<ProdEntriesSearchCubit>(
create: (context) => ProdEntriesSearchCubit(),
),
],
child: MyApp(),
),
);
2
You can pass an instance of the bloc through the nav route and use BlocProvider.value to provide the same instance of the bloc.
//nav method
Navigator.of(context).pushNamed(
'/entry_prod_search_screen',
arguments: context.read< ProdEntriesSearchCubit >(),
);
//in the navigated route screen
final bloc = ModalRoute.of(context).settings.arguments;
return MultiBlocProvider(
providers: [
BlocProvider.value(
value: bloc,
),
],
child: ...,
);
I am having a bit of issue loading images from my Firebase Firestore database into my Flutter app. I am sort of stuck on this since I don't know what to do currently. I have seen a way to use NetworkImage to download an image on a list tile but it only does one image. I am trying to figure out a way to use a separate image for each item in the Firestore database.
I have hooked up the image URL from the Firebase Storage to Firestore under the imageURL key which is a String. But I am still struggling to figure out how to do this. I have also downloaded all the dependencies such as Firebase and Firestore. If anyone does offer to help or shares a tip on how they have worked with something similar to this, it'd be greatly appreciated! :)
class HomeTab extends StatefulWidget {
#override
_HomeTabState createState() => _HomeTabState();
}
class _HomeTabState extends State<HomeTab> {
#override
Widget build(BuildContext context) {
// This will reference our posts collection in Firebase Firestore NoSQL database.
CollectionReference posts = FirebaseFirestore.instance.collection('posts');
return Center(
child: Container(
width: 250,
child: StreamBuilder<QuerySnapshot>(
// Renders every post from the Firebase Firestore Database.
stream: posts.snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) {
return Text('Something went wrong');
}
if (snapshot.connectionState == ConnectionState.waiting) {
return Text("Loading");
}
return new ListView(
children: snapshot.data.docs.map((DocumentSnapshot document) {
return Card(
margin: EdgeInsets.all(20.0),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15.0),
),
elevation: 10,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
// Renders the title on every single listview.
title: new Text(document.data()['title']),
// leading: new NetworkImage(''),
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => ReaderPage(),
),
);
},
),
],
// Added Column
// In case if things don't work, go back to this.
// child: new ListTile(
// title: new Text(document.data()['text']),
// ),
),
);
}).toList(),
);
},
),
),
);
}
}
Here is my Firebase Storage
My Firestore database
The NetworkImage class is not a flutter widget class. It is responsible for downloading the image from the URL provided. It inherits from the ImageProvider class.
From the flutter docs:
Fetches the given URL from the network, associating it with the given scale.
Use Image.network for a shorthand of an Image widget backed by NetworkImage.
You should be using the Image.network named constructor of the Image widget to create a flutter widget used to display image with help of NetworkImage.
You should replace the NetworkImage in your widget tree with:
new Image.network("https://i.stack.imgur.com/W98kA.png")
I have a chat (ListView) with messages that I only want to load as needed.
So when the chat is initially loaded I want to load the last n messages and when the user scrolls up I want to fetch older messages also.
Whenever a new message arrives in the firebase collection it should be added to the ListView. I achieved this by using a StreamBuilder that takes the stream of the last n messages where n is a variable stored in the state that I can increase to load more messages (it is an argument to the function that gets the stream of the last n messages).
But with my current implementation the problem is that even though more messages are fetched and added to the listview when I scroll up, it then immediately jumps back to the bottom (because the listview is rebuilt and the scrollposition isn't preserved). How can I prevent this from happening?
This issue is not related to ListView or the scroll position. Those are kept with automatically. The issue must be somewhere else in your code. Check my example below to see how having a list, adding new items and then resetting it, will maintain the scroll position or move to the right place:
class ListViewStream60521383 extends StatefulWidget {
#override
_ListViewStream60521383State createState() => _ListViewStream60521383State();
}
class _ListViewStream60521383State extends State<ListViewStream60521383> {
List<String> _itemList;
#override
void initState() {
resetItems();
super.initState();
}
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Expanded(
child: ListView.builder(
reverse: true,
itemCount: _itemList.length,
itemBuilder: (context, index){
return Container(
height: 40,
child: Text(_itemList[index]),
);
},
),
),
Row(
children: <Widget>[
RaisedButton(
onPressed: addMoreItems,
child: Text('Add items'),
),
RaisedButton(
onPressed: resetItems,
child: Text('Reset items'),
)
],
)
],
);
}
void addMoreItems(){
int _currentListCount = _itemList.length;
setState(() {
_itemList.addAll(List.generate(60, (index) => 'item ${index + _currentListCount}'));
});
}
void resetItems(){
setState(() {
_itemList = List.generate(60, (index) => 'item $index');
});
}
}
Using FirestoreListView you do that easily.
Refer this for more info https://www.youtube.com/watch?v=si6sTuVZxtw
I'm experimenting with firebase on flutter. Found this initialRoute property in the documentation. I'm using it like this:
#override
Widget build(BuildContext context) {
// TODO: implement build
return new MaterialApp(
color: Colors.yellow,
routes: <String, WidgetBuilder>{
"/login":(BuildContext context) => new SignIn(),
},
initialRoute: "/login",
home: DefaultTabController(
length: 4,
...
In this SignIn Activity is the GoogleLogin and I was initially using Navigator to switch between the two activities. I want to know what triggers will be used in the SignIn activity to switch between screens? Do I still use a Navigator?
In the SignIn activity I have a proceed button to perform this switch:
new MaterialButton(onPressed: (){
Navigator.pop(context);
Navigator.push(context,
MaterialPageRoute(builder: (context)=> TabLayoutDemo.fromTabLayoutDemo(_auth, _googleSignIn)));
},
color: Colors.green,
splashColor: Colors.greenAccent,
child: const Text("Proceed"),
),
But what this does is launch the SignIn screen again after login completes.
You can still use Navigator on your SignIn Activity to navigate between screens. However, you may need to check if there's a user already signed-in to skip the SignIn page. To check the currently logged in user, you can use FirebaseAuth.instance.currentUser.
I'm writing a Flutter application which integrates Firebase Authentication.
The problem
I would like to integrate in the best and most optimal way possible the authentication checking if the user is authenticated in the moment the app is launched. If the user is authenticated the app opens the normal home page, otherwise the authentication page is shown. After the authentication the app should redirect the user to the normal home page. For obvious reasons the user mustn't have the possibility to tap the back button and go back to the authentication page.
What I've done so far
At the moment the application checks in the main() if the user is authenticated, and, if it is so, it creates a MaterialApp with, as home, the main page of the application. In that case, the '/' of the app is the home page. If it is not, the app creates a MaterialApp with, as home, the authentication screen. In that case, however, the '/' is the welcome screen, and so I can't use
Navigator.of(context).popUntil(ModalRoute.withName('/'))
(which, in fact, happens to be quite necessary and useful), because the '/' is not the home page, and, moreover, the user could tap the back button and get back to the welcome screen.
The question
What am I losing? Am I completely wrong, and there is a totally different way of doing what I want to do, or the base is correct? If so, how can I implement what I would like to?
Thanks in advance.
You're looking for Navigator.pushReplacement or Navigator.pushReplacementNamed.
https://docs.flutter.io/flutter/widgets/Navigator-class.html
https://docs.flutter.io/flutter/widgets/Navigator/pushReplacement.html
https://docs.flutter.io/flutter/widgets/Navigator/pushReplacementNamed.html
Here's a quick sample code.
class FirstScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Column(
children: [
Text("First screen"),
RaisedButton(
child: Text("Go to second screen"),
onPressed: () => _goToSecondScreen(context),
),
],
);
}
_goToSecondScreen(context) {
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (context) => SecondScreen()));
}
}
class SecondScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Column(
children: [
Text("Second screen"),
RaisedButton(
child: Text("Go to first screen"),
onPressed: () => _goToFirstScreen(context),
),
],
);
}
_goToFirstScreen(context) {
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (context) => FirstScreen()));
}
}