Flutter:How to get a value from flutter/provider change notifier - flutter

I have program where I need to add a count next to a bottom navigation icon. So, I am trying to use the ChangeNotifierProvider and wrap it around my root widget. How do I get the count from the IterableModel to my widget?
MAIN.APP
ChangeNotifierProvider(create: (_) => IterableModel()),
HOME.DART
BottomNavigationBarItem(
icon: Container(
margin: EdgeInsets.only(top: 20.0),
child: ImageWithBadge(
text: 'notification',
iconData: Icons.mail,
notificationCount: "NEED MESSAGE COUNT,
),
),
),
IterableModel.dart
class IterableModel extends ChangeNotifier {
List<InAppMessages> inAppMessages;
int cnt;
IterableModel({this.inAppMessages});
void messageCnt(List inAppMessages) {
cnt = inAppMessages.length;
notifyListeners();
}
IterableModel.fromJson(Map<String, dynamic> json) {
if (json['inAppMessages'] != null) {
inAppMessages = [];
json['inAppMessages'].forEach((v) {
inAppMessages.add(new InAppMessages.fromJson(v));
});
messageCnt(inAppMessages);
}
}
}
I think i need a consumer and builder but I don't now where to place it

final count = Provider.of<IterableModel>(context).cnt;
Insert this line at the top of your build method than it would be accessible but also rebuild the entire widget one better aproach is to use Consumer
Consumer<IterableModel>(
builder: (context,model,_) => BottomNavigationBarItem(
icon: Container(
margin: EdgeInsets.only(top: 20.0),
child: ImageWithBadge(
text: 'notification',
iconData: Icons.mail,
notificationCount: model.cnt.toString(),
),
),
),
)

Related

What can I do to make my ListView stop incrementing the data every time I open it?

My first Flutter project, which is a tricycle booking system, has just begun. Using the ListView widget, I wanted to display all of the active passengers that are saved in my Firebase Database. However, when I attempted to display it and place it in a List, all functions are working fine at first click. When you click the button to view the ListView a second time, all of the saved data are replicated. The list continues after my third click and grows by three. The image below illustrates what takes place when I repeatedly click on the ListView.
These are the blocks of code that are utilized for this functionality:
CODE for Functionality
retrieveOnlinePassengersInformation(List onlineNearestPassengersList) async
{
dList.clear();
DatabaseReference ref = FirebaseDatabase.instance.ref().child("passengers");
for(int i = 0; i<onlineNearestPassengersList.length; i++)
{
await ref.child(onlineNearestPassengersList[i].passengerId.toString())
.once()
.then((dataSnapshot)
{
var passengerKeyInfo = dataSnapshot.snapshot.value;
dList.add(passengerKeyInfo);
print("passengerKey Info: " + dList.toString());
});
}
}
CODE for the UI
body: ListView.builder(
itemCount: dList.length,
itemBuilder: (BuildContext context, int index)
{
return GestureDetector(
onTap: ()
{
setState(() {
chosenPassengerId = dList[index]["id"].toString();
});
Navigator.pop(context, "passengerChoosed");
},
child: Card(
color: Colors.grey,
elevation: 3,
shadowColor: Colors.green,
margin: EdgeInsets.all(8.0),
child: ListTile(
leading: Padding(
padding: const EdgeInsets.only(top: 2.0),
child: Icon(
Icons.account_circle_outlined,
size: 26.sp,
color: Color(0xFF777777),
),
),
title: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Row(
children: [
Text(
dList[index]["first_name"] + " " + dList[index]["last_name"],
style: TextStyle(
fontFamily: "Montserrat",
fontSize: 18.sp,
fontWeight: FontWeight.bold,
color: Colors.black,
),
),
Icon(
Icons.verified_rounded,
color: Color(0xFF0CBC8B),
size: 22.sp,
),
],
),
],
),
),
),
);
},
),
Expected Result:
Actual Result AFTER CLICKING MANY TIMES:
Made a demo for you how to call function once on load
class CustomWidgetName extends StatefulWidget {
const CustomWidgetName({Key? key}) : super(key: key);
#override
State<CustomWidgetName> createState() => _CustomWidgetNameState();
}
class _CustomWidgetNameState extends State<CustomWidgetName> {
List? dList = [];
void myDataFunction() async {
// do your data fetch and add to dList
final newList = [];
setState(() {
dList = newList;
});
}
#override
void initState() {
super.initState();
myDataFunction(); // Call your async function here
}
#override
Widget build(BuildContext context) {
return Scaffold();
}
}
Try this solution.
Update SelectNearestActiveDriversScreen() like this:
class SelectNearestActiveDriversScreen extends StatefulWidget
{
DatabaseReference? referenceRideRequest;
final List list;
SelectNearestActiveDriversScreen({this.referenceRideRequest, required this.list});
#override
State<SelectNearestActiveDriversScreen> createState() => _SelectNearestActiveDriversScreenState();
}
In homepage.dart, declare List dList = [];, then change line 378 like this:
Navigator.push(context, MaterialPageRoute(builder: (c)=> SelectNearestActiveDriversScreen(list: dList)));
In SelectNearestActiveDriversScreen(), replace all dList with widget.list.
Finally, if you are using variables in a specific file declare them in that file(not in another file) or pass them in the constructor of the class / file / widget /screen you are calling.
If you would rather use global variables and state managers go for packages like GetX.

How to prevent not to rebuild UI inside Index Stack

I'm using index stack in home screen to show different screen at one time.
My Problem is that child widget is rebuilt again and make API call when ever I re-enter to any screen
home screen code:
final _currentPage =
context.select<MenuProvider, int>((provider) => provider.currentPage);
void _onItemTapped(int index) {
Provider.of<MenuProvider>(context, listen: false)
.updateCurrentPage(index);
}
List<MenuItem> mainMenu = [
MenuItem(
AppLocalizations.of(context)!.sd_title,
'Home',
Icons.home,
0,
),
MenuItem(
AppLocalizations.of(context)!.profile_title,
'Profile',
Icons.person,
1,
),
MenuItem(
AppLocalizations.of(context)!.sd_calculator,
'Calculator',
Icons.calculate_rounded,
2,
),
];
var screens = [
mainMenu[_currentPage].index == 0 ? const HomeFragment() : Container(),
mainMenu[_currentPage].index == 1 ? const Profile() : Container(),
mainMenu[_currentPage].index == 2
? LoanCalculatorScreen(isHome: true)
: Container(),
];
var container = Container(
alignment: Alignment.center,
color: Colors.white,
child: FadeIndexedStack(
index: mainMenu[_currentPage].index,
children: screens,
),
);
Container is used in body of Scaffold.
Bottom Navigation:
bottomNavigationBar: BottomNavigationBar(
items: mainMenu
.map(
(item) => BottomNavigationBarItem(
label: item.bottomTitle,
icon: Icon(
item.icon,
),
),
)
.toList(),
currentIndex: _currentPage,
selectedItemColor: Colors.amber[800],
onTap: _onItemTapped,
)
Home screen: As statefull to make a call on init state;
#override
void initState() {
var homeData = Provider.of<HomeProvider>(context, listen: false);
homeData.getOffersAndPartnersSlider();
super.initState();
}
I'm using Provider for state management and API call.
any suggestion will help me to write better code and make good performance.
I've Try Page View also same thing happen the inside child is rebuilt.
I just want to make The API call once.
To fix this I've made changes with Pages.
Declare list of pages above built method with PageStorageKey.
final List<Widget> pages = [
const HomeFragment(
key: PageStorageKey('Page1'),
),
const Profile(
key: PageStorageKey('Page2'),
),
Lo
LoanCalculatorScreen(key: const PageStorageKey('Page'), isHome: true),
];
And Call pages inside Indexed Stack Widgets Children
Container(
alignment: Alignment.center,
color: Colors.white,
child: FadeIndexedStack(
index: mainMenu[_currentPage].index,
children: pages,
),
)

Stack with global z-index?

I have a DataTable in which some cells have links. Ideally, I would like to fetch a preview about the link's content whenever hovering over the link, which I was able to achieve using the Stack widget. However, since the stacked preview is inside the DataCell, it seems like I'm not able to raise its "z-index" to be on top of the rest of the table.
Is this not possible with Flutter, or is there a way around it?
The only way I imagine this working, without something to update a global z-index, would be for the cell to update a global state and then have the thumbnail preview appear on a Stack above the DataTable level. But I wish there was a less clunkier way to do it...
3 widgets I've tried but to no avail — they might work, I don't know —:
Tooltip
Overlay
FloatingActionButton
My whole app is here, and the precise commit is 0303732. The relevant code is this ClickableLink widget:
import 'package:flutter/material.dart';
import 'package:flutter/gestures.dart';
import 'package:url_launcher/url_launcher.dart';
import '../schema/links.dart';
#immutable
class ClickableLink extends StatefulWidget {
const ClickableLink({
Key? key,
required this.link,
this.linkText,
this.color = Colors.blue,
}) : super(key: key);
final Link link;
final String? linkText;
final Color color;
#override
State<ClickableLink> createState() => _ClickableLinkState();
}
class _ClickableLinkState extends State<ClickableLink> {
Widget hoverWidget = const SizedBox.shrink();
void _fetchPreview(PointerEvent pointerEvent) {
setState(() {
if (widget.link.host == 'online-go.com' && widget.link.prePath == 'game') {
hoverWidget = Positioned(
top: 25,
child: Image.network('https://online-go.com/api/v1/games/${widget.link.id}/png'),
);
}
});
}
void _onExit(PointerEvent pointerEvent) {
setState(() {
hoverWidget = const SizedBox.shrink();
});
}
#override
Widget build(BuildContext context) {
return MouseRegion(
onHover: _fetchPreview,
onExit: _onExit,
child: Stack(
clipBehavior: Clip.none,
children: [
SelectableText.rich(
TextSpan(
text: widget.linkText ?? widget.link.id,
style: TextStyle(color: widget.color),
recognizer: TapGestureRecognizer()
..onTap = () async => launch(widget.link.completeLink),
),
),
hoverWidget,
],
),
);
}
}
The problem here is due to the fact that your Stack widget, defined inside ClickableLink, will be at a "lower" point (inside your app widget tree) than every other GameResultCell.
So even the higher z-index will still be behind the other GameResultCells.
To fix this I would reccomend changing your structure and define an higher point in your structure to show the preview.
Another way could be using a library to nest your preview inside a tooltip. Take a look at this one for example:
just_the_tooltip: ^0.0.11+2. With this package, you could even use a StatelessWidget.
The result here is more similar to what I suppose you were expecting.
class ClickableLink extends StatelessWidget {
#override
Widget build(BuildContext context) {
return JustTheTooltip(
content: Image.network(
'https://online-go.com/api/v1/games/${widget.link.id}/png',
),
child: SelectableText.rich(
TextSpan(
text: widget.linkText ?? widget.link.id,
style: TextStyle(
color: widget.color ??
(DogempTheme.currentThemeIsLight(context)
? const Color(0xff1158c7)
: Colors.orange.withOpacity(0.85)),
),
recognizer: TapGestureRecognizer()
..onTap = () async => launch(widget.link.completeLink),
),
),
);
}
}
Lastly you could use a Dialog, but the resulting behaviour is a bit different.
Take a look at this code if you want to try:
class _ClickableLinkState extends State<ClickableLink> {
Widget hoverWidget = const SizedBox.shrink();
void _fetchPreview(PointerEvent pointerEvent) {
showDialog(
context: context,
builder: (context) {
return Dialog(
backgroundColor: Colors.transparent,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Image.network(
'https://online-go.com/api/v1/games/${widget.link.id}/png'),
const SizedBox(
height: 16.0,
),
TextButton(
onPressed: () async => launch(widget.link.completeLink),
child: const Text('Go to complete link'))
],
),
);
},
);
}
#override
Widget build(BuildContext context) {
return MouseRegion(
onHover: _fetchPreview,
child: Stack(
clipBehavior: Clip.none,
children: [
SelectableText.rich(
TextSpan(
text: widget.linkText ?? widget.link.id,
style: TextStyle(
color: widget.color ??
(DogempTheme.currentThemeIsLight(context)
? const Color(0xff1158c7)
: Colors.orange.withOpacity(0.85)),
),
recognizer: TapGestureRecognizer()
..onTap = () async => launch(widget.link.completeLink),
),
),
],
),
);
}
}

Widgets get no update from the listener flutter

I have implemented an listener in the following way:
#override
void didChangeDependencies() {
final SlotDataProvider slotDevice = Provider.of<SlotDataProvider>(context);
spptask.streamdevice.listen((device) {
setState(() {
slotDevice._devices[0].name = device.name;
print("Device data received: ${device.name} ");
});
}, onError: (error) {
print("Error: $error.message");
});
super.didChangeDependencies();
}
I listen on a splitted controller and the print "Device data received:..." is called but the widget is not actualized. In the build method I do the following:
...
#override
Widget build(BuildContext context) {
final slotProvider = Provider.of<SlotDataProvider>(context);
final deviceProvider = Provider.of<DeviceDataProvider>(context);
Device slotDevice = slotProvider.getDevice(widget.slot);
Device device = deviceProvider.getDevice(widget.slot);
_dropdownMenuItems = buildDropdownMenuItems(deviceProvider.get());
return ListTile(
title: Row(
children: <Widget>[
SizedBox(
width: 140,
child: DropdownButton(
isExpanded: true,
disabledHint: Text(slotDevice.name),
hint: Text(slotDevice.name),
value: device,
items: _dropdownMenuItems,
onChanged: (value) {
device.setDevice(value);
slotDevice.setDevice(value);
}),
),
SizedBox(width: 10),
SizedBox(width: 60, child: Text('SLOT#${slotDevice.slot}')),
],
),
subtitle: Text(slotDevice.bdaddr, style: TextStyle(fontSize: 10.0)),
leading: SizedBox(
height: 40,
width: 35,
child: UsbBatteryImageAsset(slot: widget.slot),
),
trailing: Icon(Icons.keyboard_arrow_right),
);
}
}
What is missing in the above code. The SlotDataProvider is a fix list of "Device" with attributes such as name, id and so on.
#EDIT
The problem has to do with the combobox. If I change an other field, it works.
Usually for widgets to be rebuilt based on the data updated we use streambuilders
this will cause the widget to rebuild every time there is a change in the stream
it seams that your widget is being built once with the first listening of the data
have you tried wrapping the gridview in a stateful builder ?

while retrieving data from firebase in a flutter app

This is the code
import 'package:flutter/material.dart';
import 'Authentication.dart';
import 'photoUpload.dart';
import 'Posts.dart';
import 'package:firebase_database/firebase_database.dart';
// import 'package:flutter_blogapp/Authentication.dart';
// import 'package:flutter_blogapp/photoUpload.dart';
class HomePage extends StatefulWidget
{
HomePage
(
{
this.auth,
this.onSignedOut,
}
);
final AuthImplementation auth;
final VoidCallback onSignedOut;
#override
State<StatefulWidget> createState()
{
return _HomePageState();
}
}
class _HomePageState extends State<HomePage>
{
List<Posts> postsList = [];
#override
void initState()
{
super.initState();
DatabaseReference postsRef = FirebaseDatabase.instance.reference().child("Posts");
postsRef.once().then((DataSnapshot snap)
{
var KEYS = snap.value.keys;
var DATA = snap.value;
postsList.clear();
for(var individualKey in KEYS)
{
Posts posts = new Posts
(
DATA[individualKey]['image'],
DATA[individualKey]['desctiption'],
DATA[individualKey]['data'],
DATA[individualKey]['time'],
);
postsList.add(posts);
}
setState(()
{
print('Length : $postsList.length');
});
});
}
void _logoutUser() async
{
try
{
await widget.auth.signOut();
widget.onSignedOut();
}
catch (e)
{
print(e.toString());
}
}
#override
Widget build(BuildContext context)
{
return new Scaffold
(
appBar: new AppBar
(
title: new Text('Home'),
),
body : new Container
(
child: postsList.length == 0 ? new Text(" No Post available ") : new ListView.builder
(
itemCount: postsList.length,
itemBuilder: (_, index)
//itemBuilder: (BuildContext _, int index ) //<-----
{
return PostsUI(postsList[index].image, postsList[index].description, postsList[index].date, postsList[index].time);
}
),
),
bottomNavigationBar: new BottomAppBar
(
color: Colors.pink,
child: new Container
(
margin: const EdgeInsets.only(left: 70.0, right: 70.0),
child: new Row
(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisSize: MainAxisSize.max,
children: <Widget>
[
new IconButton
(
icon: new Icon(Icons.local_car_wash),
iconSize: 50,
color: Colors.white,
onPressed: _logoutUser,
),
new IconButton
(
icon: new Icon(Icons.add_a_photo),
iconSize: 50,
color: Colors.white,
onPressed: ()
{
Navigator.push
(
context,
MaterialPageRoute(builder: (context)
{
return new UploadPhotoPage();
})
);
},
),
],
),
),
),
);
}
// Designing Posts UI <remove from Text field><??'defaut value'>
Widget PostsUI(String image, String description, String date, String time)
{
return new Card
(
elevation: 10.0,
margin: EdgeInsets.all(15.0),
child: new Container
(
padding: new EdgeInsets.all(14.0),
child: new Column
(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>
[
new Row
(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>
[
new Text
(
date,
style: Theme.of(context).textTheme.subtitle,
textAlign: TextAlign.center,
),
new Text
(
time,
style: Theme.of(context).textTheme.subtitle,
textAlign: TextAlign.center,
), //<----
],
),
SizedBox(height: 10.0,),
new Image.network(image, fit:BoxFit.cover),
SizedBox(height: 10.0,),
new Text
(
description, //= null ? "true" : "False", //??'defaut value'
style: Theme.of(context).textTheme.subhead,
textAlign: TextAlign.center,
),
],
)
)
);
}
}
The error that i was getting
The following assertion was thrown building: A non-null String must be
provided to a Text widget. 'package:flutter/src/widgets/text.dart':
Failed assertion: line 285 pos 10: 'data != null'
Either the assertion indicates an error in the framework itself, or we
should provide substantially more information in this error message to
help you determine and fix the underlying cause. In either case,
please report this assertion by filing a bug on GitHub:
https://github.com/flutter/flutter/issues/new?template=BUG.md
When the exception was thrown, this was the stack
2 new Text package:flutter/…/widgets/text.dart:285
3 _HomePageState.PostsUI package:flutter_blogapp/HomePage.dart:184
4 _HomePageState.build. package:flutter_blogapp/HomePage.dart:100
5 SliverChildBuilderDelegate.build package:flutter/…/widgets/sliver.dart:446
6 SliverMultiBoxAdaptorElement._build. package:flutter/…/widgets/sliver.dart:1260
What can i do to retrieve the data in my app? I have tried what i can do. can some one help me in finding where am I wrong? I have started learning recently.
First thing, pull the code out of the initState and put in a different function. This will keep your initState clean.
What I can see is you're trying to update data in firestore.
What you could do is define a new function as follows:
final databaseReference = Firestore.instance;
setPostsData() async {
DocumentSnapshot snapshot = await databaseReference
.collection("NAME_OF_COLLECTION")
.document("NAME_OF_DOCUMENT")
.get();
// use this DocumentSnapshot snapshot to get the current data that is there in the document inside of your collection.
var currentData = snapshot.data;
print(currentData); // to check whats actually there and if its working...
//lets assume newPostsList is the data that you want to put in this referenced document.
//to update the data in firestore:
await databaseReference
.collection("NAME_OF_COLLECTION")
.document("NAME_OF_DOCUMENT")
.updateData({newPostsList});
}
Then you can put setPostsData in the initState.
For this to work, you might want to redesign your database structure, for example: it should be like users --> posts --> List_of_all_posts. If did this way, NAME_OF_COLLECTION will be users and NAME_OF_DOCUMENT will be posts.