How to prevent child widget redraw when parent widget perform setState()? - firebase

I have a ListView inside many stateFull widget of tree, and i cant able to separate from all parent widget.
Listview build from streambuilder(firebase).
From a large hierarchy of stateFull widget, some of them anytime perform setState widget, then child ListView also redraw and once again it will get data from firebase and also its flickering(blinking)
explain below example
StatefullWidget(
child:StatefullWidget(
child:StatefullWidget(
child:ListView()
),
),
);
There are three parent widget of Listview(), for user friendly app setState called many time in parent widget.
So i want to avoid flickering and redraw of listView(), even if the parent widget redraw(setState())

https://pub.dev/documentation/provider/latest/provider/Selector-class.html
'selector' of Provider is what you find.
But usually optimizing performance is enough for not blinking.
ListView.builder, const Widget, key, etc., https://api.flutter.dev/flutter/widgets/StatefulWidget-class.html#performance-considerations

You should use one of the state management method.
For example with getx package, you can make your widgets stateless and use getx widgets for any updating data.
This is the example of getx usage for default flutter app.
class Home extends StatelessWidget {
final controller = Get.put(Controller());
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("counter")),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
GetBuilder<Controller>(
builder: (_) => Text(
'clicks: ${controller.count}',
)),
ElevatedButton(
child: Text('Next Route'),
onPressed: () {
Get.to(Second());
},
),
],
),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: controller.increment(),
),
);
}
}
class Second extends StatelessWidget {
final Controller ctrl = Get.find();
#override
Widget build(context){
return Scaffold(body: Center(child: Text("${ctrl.count}")));
}
}

Related

Bloc isn't found in the widget tree

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: ...,
);

Struggling to load images from Firebase Firestore into ListItem in Flutter app

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")

Flutter/Firestore: How to add items to stream on scroll (preserve scrollposition when done fetching)?

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

firebase user number is showing null

i want to display the user phone number to the screen but it is showing null on the screen
String _userId;
#override
Widget build(BuildContext context) {
FirebaseAuth.instance.currentUser().then((user) {
_userId = user.phoneNumber;
});
print(_userId);
return MaterialApp(
home: Scaffold(
appBar: AppBar(),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Container(
child: Text("$_userId")
?? CircularProgressIndicator(),
You need to move your Firebase query to outside your build method. The build method is constantly being run as your Widget is rebuilt. Ideally you would implement it this way:
// _userId needs to be initiated with a blank value
String _userId = '';
#override
void initState() {
loadCurrentUser();
super.initState();
}
void loadCurrentUser(){
FirebaseAuth.instance.currentUser().then((user) {
setState(() {
_userId = user.phoneNumber;
});
});
}
This way, as soon as the information is loaded your view will be updated.
You need to use a FutureBuilder, the FutureBuilder widget comes with Flutter and makes it easy to work with async data sources. Therefore you need to do the following:
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Container(
child : FutureBuilder(
future: FirebaseAuth.instance.currentUser(),
builder: (context, snapshot) {
if (snapshot.hasData) {
print(snapshot);
return Text(snapshot.data.uid);
}
// By default, show a loading spinner.
return CircularProgressIndicator();
}
)
The future property will contain the asynchronous computation to which this builder is currently connected, possibly null. Also you should be using a Stateful Widget.
You should show more of your code but it appears you're using a StatelessWidget which means that after that Future returns, the value of _userId in the build method is not going to update by itself. The simplest way would be to use a StatefulWidget and call setState within your Future to update the value.
Also your line Text("$_userId")?? CircularProgressIndicator() won't work correctly because of the same issue, but you need to show one widget or the other depending on the value of _userId.

Flutter: Only Re-render widget where setState() was called - in nested object structure

How Flutter renders the widgets has been described in detail here.
It will compare the new with the old widget.
Therefore, Flutter usually knows how to re-render the UI in most cases.
In a nested object structure, all parent widgets inevitably change when you change the child, so they seem to be rendered new.
This leads to unattractive visual effects for the user of the app.
Is there a way to re-render only the widget in which SetState () was called?
I tried kinds of this.setState (() {}).
I have swapped out the widgets in a stateless class.
Unfortunately, both approaches do not solve the problem.
Widget _createCheckbox(int dataArrayPosition, int elementCurrentPosition, int inputCurrentPosition) {
Input inputElement = contextData.data[dataArrayPosition].element[elementCurrentPosition].input[inputCurrentPosition];
return Column(
children: <Widget>[
Row(
children: <Widget>[
Checkbox(
value: inputElement.checkBoxValue,
onChanged: (bool newValue) {
setState(() {
contextData.data[dataArrayPosition].element[elementCurrentPosition].input[inputCurrentPosition].checkBoxValue = newValue;
});
}),
Text(inputElement.labelForValue),
],
),
],
);
}
Does anyone know a way to re - render only the 'affected' widget in this example when onChanged was called?
The Flutter Package "Image_Picker" saves photos as variables of type "File".
I have converted the foto into a jSON construct. The photos were saved as BASE64.
It is possible to have the Base64 construct displayed in a widget.
Unfortunately, Flutter does not seem to notice when rendering that it's always the same image, when you use a BASE64 Image.
Therefore, the flicker effect occurs if the user enters new data.
This kind of code is working without any problems.
var picturesTaken = <File>[];
Widget _showFoto(int currentFoto) {
return Padding(
padding: const EdgeInsets.only(bottom: 10.0),
child: Container(
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Center(
child: Column(
children: <Widget>[
Image.file(picturesTaken[currentFoto])
],
),
),
),
)
);
}

Resources