How to avoid re-fetching user info from Firebase with Flutter - firebase

As soon as the user logs in my app redirects user to homepage and the homepage has a bottomNavigationBar and a AppBar
In this AppBar I display the user's avatar and the appname. To show this I use:
Stack(
children: [
FutureBuilder(
future: getAvatar(),
builder: (context, snapshot) {
if (snapshot.hasError) {
return ClipRRect(
borderRadius: BorderRadius.circular(25.0),
child: Image.asset(
'assets/default_avatar.png',
fit: BoxFit.cover,
),
);
}
if (snapshot.connectionState == ConnectionState.done) {
return Center(
child: SizedBox(
height: 45.0,
child: ClipRRect(
borderRadius: BorderRadius.circular(25.0),
child: FadeInImage.assetNetwork(
placeholder: 'assets/default_avatar.png',
image: snapshot.data['avatar'],
fit: BoxFit.cover,
),
),
),
);
}
return Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
},
),
],
It works great, but I do not find it is good practice... and every time I change page from the bottom navigation bar the avatar "reloads" (you see the CircularProgressIndicator). doesn't help that it is a stateful class
So my solution to stop this from happening is to save user data somewhere on the phone and just grab that information whenever I need it. BUT HOW
FYI, it is not only the avatar, I will display username and useremail. If anyone has an example that they can redirect me to, that would be great!
Thank you!

You're incorrectly using FutureBuilder.
The docs state
The future must have been obtained earlier, e.g. during State.initState, State.didUpdateWidget, or State.didChangeDependencies. It must not be created during the State.build or StatelessWidget.build method call when constructing the FutureBuilder. If the future is created at the same time as the FutureBuilder, then every time the FutureBuilder's parent is rebuilt, the asynchronous task will be restarted.
You're clearly not doing that here as you are obtaining the Future during build, making it expected that any time that build is called you're seeing the loading indicator.
Obtain the Future in initState and pass that Future to your FutureBuilder.
However, if you're also using a PageView or equivalent to manage your pages, you'll also need to use the AutomaticKeepAliveClientMixin and call super.build(context).

Related

How to track google analytics event in a flutter widget?

I am looking to track how many times a button/expansion tile is clicked.
I tried reading the docs but still can't get the concept of it. In the flutter fire docs, it stated to log custom events by calling the logEvent method hERE.
await FirebaseAnalytics.instance
.logEvent(
name: 'view_product',
parameters: {
'product_id': 1234,
}
);
However, what if it is an expansion tile widget (like the code below) or even a button? How do I track the event whenever a user pressed the expansion tile widget/button in flutter and the event will be tracked in my Google Analytics? (I have already set up firebase and google analytics packages, just struggling on the code part)
ExpansionTile(
title: RichText(
textAlign: TextAlign.justify,
text: TextSpan(
children: <TextSpan>[
TextSpan(
text: "Diploma in Business | ",
style: TextStyle(color: Colors.black),
),
TextSpan(
text: "Taylor\u0027s",
style: TextStyle(
color: Colors.black, fontWeight: FontWeight.bold),
),
],
),
),
),
Appreciate any help or guidance I can get, thank you in advance!
First of all make your ExpansionTile clickable. Wrap your ExpansionTile with Inkwell and its onTap property call logevent function
Inkwell(
onTap:(){
sendAnalyticsEvent("User clicked expansionTile");
},
child : ExpansionTile(
//write your expansion tile
),
);
And this the analytics custom event function.
final FirebaseAnalytics _analytics = FirebaseAnalytics();
Future sendAnalyticsEvent(
{String eventName, String? clickevent}) async {
await _analytics.logEvent(
name: '${eventName}',
parameters: <String, dynamic>{
'clickEvent': clickevent
},
);
}
Call this function like this
sendAnalyticsEvent( eventName : "expansionTile_one", clickevent : "User clicked expansionTile one")
sendAnalyticsEvent( eventName : "expansionTile_two", clickevent : "User clicked expansionTile two")
Check this click event in analytics debugView.
Inside your ExpansionTile, you can use:
onExpansionChanged: (value){
sendAnalyticsEvent("User clicked expansionTile");
}

how can i get option in doc(path) in firestore

Flutter - Firestore
i have this track in streamBuilderWidget
StreamBuilder<QuerySnapshot>(
stream:FirebaseFirestore.instance.collection("usersMsgs").doc(????).collection("chat").snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (!snapshot.hasData) {
return Center(
child: circulearProgress(),
);
} etc.....
in doc() part How can i tell him go to path which is contains("123") or contains("456")
Note : there already one doc("123456") in same collection is existing..
and those numbers is only as example for real user's Id's
For any help I would be very grateful
thanks
I'm not sure I just give some ideas
You can use a textfield and add it inside the doc()
Add radio buttons and use the doc()

Animation in Streambuilder to display Firebase Content

I want to add some animation upon addition or removal of an object (that is constructed using streambuilder by reading data from my firebase database). How the progression works is
User adds task => Task gets added to firebase =>Streambuilder detects change and rebuilds UI (jaggedly due to no animation) => User removes task => Task gets removed from firebase => Streambuilder detects change and rebuilds UI (Again jaggedly as their is no animation)
I want to add animation at the two points where streambuilder is rebuilding the UI and adding the Todo task. Is there any way to do this?
I have researched about the package animated_stream_list but really could'nt understand how I could incorporate it into my code. Any insight about that would be wonderful.
Code:
StreamBuilder(
stream: FirebaseFirestore.instance
.collection('Users')
.doc(FirebaseAuth.instance.currentUser.uid)
.collection('UserTasks')
.orderBy("Timestamp", descending: true)
.snapshots(),
builder:
(BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
}
return ListView(
children: snapshot.data.docs.map((document) {
//Container that containes text (Make function later on) START
return GestureDetector(
onDoubleTap: () {
//Deleting task when user taps on it
deletetask("${document.data()['Todo']}");
},
child: AnimatedContainer(
// UI elements .....
child: Container(
child: Text(
"${document.data()['Todo']}")
)
.....
), //Animated container end
); //Gesture Detector end
}).toList(),
); //Listview
},
), //StreamBuilder
AnimatedSwitcher might help here. It animates between widgets automatically whenever the child changes. In your case, inside stream builder, wrap your widget with animated switcher. Check this for more information. Animated Switcher
There is a good video explanation of how to use it in the page. For more info, refer to the documentation.
try using AnimatedList.
use snapshot.data.docChanges to detect changes and
call insertItem or removeItem based on docChange.type
maybe something like this
snapshot.data.docChanges.forEach((docChange) {
if (docChange.type == DocumentChangeType.added) {
AnimatedListState.insertItem();
} else if (docChange.type == DocumentChangeType.removed) {
AnimatedListState.removeItem();
}
});
When anything updates then StreamBuilder rebuilds the widget automatically. Internally, StreamBuilder calls State.setState. So, according to me, if you want to add an animation when the item is deleted, you have to add it on the Gesture detection. In your code snippet, you had added on GestureDetection that will remove the item on double click. So, you can do one thing. When the user double clicks, play the animation for some seconds and then call the function to delete the item. After that, StreamBuilde will rebuild itself and displays the updated content.
Hope this will solve your use case. If you have any problem further, please comment below.

Detect when user interact with the app (Flutter)

I need a little help there.
I want to disconnect the user from the app when he has been inactive for 5 minutes. So I used this solution : Detect when user is not interacting the app in Flutter
It is perfectly working when the user tap on a widget that is not clickable. For example, when I tap on Text widget, the timer is restarting without an issue, but when I tap on a RaisedButton or ListTile, the timer is not restarting.
So, I'm wondering how it is possible for example with this code :
GestureDetector(
onTap: () => print("gestureDetector"),
child: RaisedButton(
onPressed: () => print("raisedButton"),
),
),
to print "gestureDetector" AND "raisedButton".
Thank you in advance for your help, cheers ;)
Use a Listener instead of GestureDetector.
Listener(
onPointerDown: (e) => print('listener'),
child: RaisedButton(
onPressed: () => print('raisedButton'),
),
),

Flutter state management issue

My problem is related to the provider pattern/library.
I have created some state classes as follows (ive replaced content/var names to try and keep it simple)
class State1 with ChangeNotifier{
String _s;
set s(String newS){
_s = newS;
notifyListeners();
}
}
and then I use a multiprovider to pass it (create the objects in the widget init which acts as parent to everything in the app.)
child: MultiProvider(
providers: [
ChangeNotifierProvider(builder: (context) => state1),
],
child: Stack(
alignment: Alignment.center,
children: stackChildren,
),
Which is then accessed in the children widgets build method using
state1Var = Provider.of<State1>(context);
And all of this works fine..
My issue is when I get to using a navigation push I can no longer access the state.
onPressed: (() {
Navigator.push(
contextField,
MaterialPageRoute(builder: (context) => NewPage()),
);
}),
As i get this error
Could not find the correct Provider<State1> above this NewPage Widget...
I did manage to get it using this
Navigator.push(
context,
MaterialPageRoute(builder: (context) => Consumer(builder: (),child: NewPage(),)),
);
But when I popped the widget using navigator.pop() the state couldnt be used as it said it had been disposed. Am i doing this incorrectly or is there something i am missng?
Sorry if i've made this complicated. I had to remove a lot of code.
Thanks for your help :)
Probably you have a context hierarchy problem. Wrap your MaterialApp with MultiProvider or your Provider must be on top of the Route you are getting the provider within.
Otherwise, you can wrap your Scaffold with Consumer widget. What Consumer does is, simply it's a builder for establishing a connection between your Provider and Route by creating yet another context and lets you to obtain Provider class via InheritedWidget.

Resources