How update data from different widgets Flutter - flutter

I got a list of PostItem with a FutureBuilder.
PostItem got a like button and a like count.
When a click on the post, I go into its details. And I can like the post on this screen.
Here is when I click on the like button in detail screen :
Future<void> _updateLike() async
{
PhpPost phpPost = PhpPost();
phpPost.posteModel = widget.postModel;
if(_isLike)
{
String res = await phpPost.unlikePost();
if(res=="OK")
{
setState(() {
_isLike = false;
});
}
}
else
{
String res = await phpPost.likePost();
if(res=="OK")
{
setState(() {
_isLike = true;
});
}
}
widget.postModel.isLike = _isLike;
}
The screen detail update nicely but when I go back at the home screen the post item not updated the like.
Here is how I go to detail from post item :
Navigator.pushNamed(context, '/post_detail', arguments: widget.postModel);

setState here is a local for only this widget and wont rebuild the home page
a simple solution is to try call setState after the await Navigator.pushNamed
which will call setState for the home page after we close the post page
await Navigator.pushNamed(context, '/post_detail', arguments: widget.postModel);
setState((){});
this will work if you are calculate the like count at the build method or you should re-calculate it inside setState
a better solution is to not use a setState at all for handling a user-data change
and use state management solution like provider with ChangeNotifier, bloc or riverpod
which you will have a controller that will change the data and update the widget

Related

Clear All button onPressed triggered but does not clear the list items and rebuild the listview

I have a widget button Clear All with onPressed():
onPressed: () {
setState(() {
MyCart().clearEverything();
_buildListItem();
});
},
The clear all will trigger code below
clearEverything() {
cartList.clear();
print(cartList);
notifyListeners();
print('hell..');
}
Even though onPressed is triggered, it does not clear the list items and rebuild the listview. What is wrong?
You should make the cartList static and use it like MyCart.cartList. Right now it will always make a new instance of it every time you call MyCart(), so the new list will be cleared but when you again call MyCart().cartList the new list is not cleared.
class MyCart {
static List<String> cartList = [...];
clearEverything() {
cartList.clear();
print(cartList);
notifyListeners();
print('hell..');
}
}

How to force initState every time the page is rendered in flutter?

I am adding some data into the SharedPreferenceson page2 of my app and I am trying to retrieve the data on the homepage. I have used an init function on page 1 as follows:
#override
void initState() {
super.initState();
_getrecent();
}
void _getrecent() async {
final prefs = await SharedPreferences.getInstance();
// prefs.clear();
String b = prefs.getString("recent").toString();
Map<String, dynamic> p = json.decode(b);
if (b.isNotEmpty) {
print("Shared pref:" + b);
setState(() {
c = Drug.fromJson(p);
});
cond = true;
} else {
print("none in shared prefs");
cond = false;
}
}
Since the initState() loads only once, I was wondering if there was a way to load it every time page1 is rendered. Or perhaps there is a better way to do this. I am new to flutter so I don't have a lot of idea in State Management.
you can override didChangeDependencies method. Called when a dependency of the [State] object changes as you use the setState,
#override
void didChangeDependencies() {
// your codes
}
Also, you should know that using setState updates the whole widget, which has an overhead. To avoid that you should const, declaring a widget const will only render once so it's efficient.
First thing is you can't force initState to rebuild your widget.
For that you have setState to rebuild your widget. As far as I can
understand you want to recall initState just to call _getrecent()
again.
Here's what you should ideally do :
A simple solution would be to use FutureBuilder. You just need to use _getrecent() in FutureBuilder as parent where you want to use the data you get from _getrecent(). This way everytime your Widget rebuilds it will call _getrecent().
You simply use setState() methode if you want to update widget. Here's documentation for this https://api.flutter.dev/flutter/widgets/State/setState.html
init function will render it only once initially, to avoid that -
You can use setState( ) method to re-render your whole widget.

Flutter GETX: How to remove Initialized Controller every time we navigate to other page/routes

newbie here. How do I re-run onInit() every time I push back to my screen? onInit() runs only once but navigating back to a previous screen does not delete the controller which was initialized (FetchData) hmmm..
I'm only using Get.back() every time I want to pop page, and
Get.toNamed() every time I want to navigate on a named route
the only thing I want to happen is to delete the Initialized controller (FetchData) every time I pop the page
but I have no Idea how to do it.
my GetxController
class FetchData extends GetxController {
RxList items = [].obs;
#override
onInit() {
fetchData();
super.onInit();
}
Future<void> fetchData() async {
var result = await http.get("api.url");
items.value = result.body;
}
}
Thanks in advance!
You can use:
Get.delete<FetchData>();
When I logout Get.off, Get.offUntil, Get.offAndToNamed methods doesnt remove my GetXController from memory.
then I tried below code and everything works fine.
Get.offUntil(GetPageRoute(page: () => Login()), ModalRoute.withName('toNewLogin') );
Timer(Duration(milliseconds: 300), ()=>Get.delete<MainPageController>());
You cannot put method fetchData() on super.onInit(). When you use Get.offAllName(), Get.offAndToName(), Get.offAll(), etc... Method fetchData() is still kept in memory => Cannot dispose or close it.
FIX:
class FetchData extends GetxController {
RxList items = [].obs;
#override
onInit() {
super.onInit(); // <--- swap code here
fetchData(); // <--- swap code here
}
Future<void> fetchData() async {
var result = await http.get("api.url");
items.value = result.body;
}
}
You can use
Get.offAndToNamed(url)
The onInit is only called once. You can use another method to run when back from another screen, for example, when call the new screen you can await until it closes and then call your method again:
//go to new screen
await Get.toNamed(screenName);
//after run my method
controller.fectchData();
if you want call the method only in some cases you can pass a bool back to ask if needs reload:
Get.back(result: true);
and in the screen that called:
//go to new screen
final result = await Get.toNamed(screenName);
if(result != null && result == true)//after run only if needed
controller.fectchData();
You can use
Get.deleteAll();
This will remove all the Initialized controllers instances from your memory.
Use Get.offAllNamed. It will remove all controllers and create only the final destination route controller. Tested with get: ^4.3.8
Get.offAllNamed("your final route");
The simple answer is Get.delete<FetchData>(); if you had created like Get.put(FetchData())
But If you have created like Get.put(FetchData(), permanent: true); or by LazyPut you can delete that by forcing Get.delete<FetchData>(force: true);
I hope this helps you.

How to close a screen from PageView class flutter

Greeting,
I have a really specific question to ask. I have to explain it with steps and pictures so there they are.
I have an app with three screens:
Main Feed Screen,
Main Chat and Requests Screen,
Main Profile Screen,
And they are all a part of a PageView. This PageView class is controlled inside of a class called main_tab_controller.dart. In that class, in initState(), I have a Firebase Messaging method that is called every time I get a notification (onMessage). So, every time I get this notification, I show an overlay that looks like this.
And it works perfectly on these three main screen. If it's a chat notification, I will direct the PageView to the second screen i.e MainChatAndRequest Screen, and open the chat screen. If it's a request notification, I will direct the PageView to the second screen i.e MainChatAndRequest Screen, and open the requests screen.
But the issue that I am having is the following. In my MainFeedScreen and MainProfileScreen, I have some other screens that I open. For example in MainFeedScreen, I open UserDetailsScreen or FilterScreen. Or in the MainProfileScreen, I open SettingsScreen or EditUserProfileScreen.
So my question is: For example, if I navigate to MainProfileScreen and in that screen open SettingsScreen, and I get the overlay top message, how do I close the SettingsScreen that is currently open and navigate back to the second screen i.e MainChatsAndRequestsScreen from the Firebase Messaging Function that is in initState() of main_tab_controller.dart that is the parent to all of the other screens.
You have the Image Below:
I have tried everything, Navigator.popUntil(context), Navigator.pushReplacement(context), used Navigator.pushNamed(context) but nothing worked. If someone can help me, it would be much appreciated.
Just to give you the better undertanding of the screens:
The Parent Screen is the PageView with three screens:
Main Feed Screen
Main Chat and Requests Screen
Main Profile Screen
and then in Main Feed Screen you have:
Filters Screen
Profile Details Screen
in Main Chat and Requests Screen you have two TabBar Screens:
Chats Screen
Requests Screen
and in Main Profile Screen you have:
Settings Screen
Edit Profiles Screen
PageView Code Snippet:
#override
void initState() {
pageController = PageController(initialPage: _currentIndex);
chatAndRequestController = TabController(length: 2, vsync: this);
var chatAndRequestProvider =
Provider.of<ChatAndRequestProvider>(context, listen: false);
super.initState();
fbm.requestNotificationPermissions();
fbm.configure(
onMessage: (Map<String, dynamic> message) async {
print("onMessage: $message");
bool isRequest;
var mode = (Platform.isIOS) ? message['mode'] : message['data']['mode'];
var imageUrl = '';
switch (mode) {
case 'chat':
isRequest = false;
imageUrl =
chatAndRequestProvider.chatsList.first[kProfilePictureUrl];
break;
case 'sentRequest':
isRequest = true;
imageUrl = (Platform.isIOS)
? message['profilePictureUrl']
: message['data']['profilePictureUrl'];
break;
case 'acceptRequest':
isRequest = false;
imageUrl = (Platform.isIOS)
? message['profilePictureUrl']
: message['data']['profilePictureUrl'];
break;
default:
isRequest = false;
break;
}
AudioCache player = new AudioCache();
const alarmAudioPath = "sounds/notification_sound.mp3";
player.play(alarmAudioPath);
print('Show this ting');
if (_currentIndex != 1) {
if (!isDialogOpen) {
isDialogOpen = true;
_showAnimatedBox(
context,
(Platform.isIOS)
? message['aps']['alert']['title']
: message['notification']['title'],
(Platform.isIOS)
? message['aps']['alert']['body']
: message['notification']['body'],
imageUrl,
isRequest,
);
}
}
},
onLaunch: (Map<String, dynamic> message) async {
print("onLaunch: $message");
},
onResume: (Map<String, dynamic> message) async {
print("onResume: $message");
},
);
notificationPlugin
.setListenerForLowerVersions(onNotificationInLowerVersions);
notificationPlugin.setOnNotificationClick(onNotificationClick);
_children.addAll([
MainFeedScreen(
analytics: widget.analytics,
observer: widget.observer,
latitude: widget.latitude,
longitude: widget.longitude,
),
MainChatAndRequestScreen(
analytics: widget.analytics,
observer: widget.observer,
pageContoller: chatAndRequestController,
),
MainProfileScreen(analytics: widget.analytics, observer: widget.observer),
]);
}
Future _showAnimatedBox(context, topText, bottomText, imageUrl, isRequest) {
showDialog(
context: context,
builder: (BuildContext builderContext) {
_timer = Timer(Duration(seconds: 4), () {
Navigator.of(context).pop();
isDialogOpen = false;
});
return Dismissible(
key: Key('dismissible'),
direction: DismissDirection.up,
onDismissed: (_) {
Navigator.of(context).pop();
isDialogOpen = false;
},
child: FunkyNotification(
() {
var chatAndRequestProvider =
Provider.of<ChatAndRequestProvider>(context, listen: false);
// var contextProv =
// Provider.of<ContextProvider>(context, listen: false);
chatAndRequestProvider.setAreThereNewChatsAndRequestFalse();
if (isRequest) {
pageController.jumpToPage(1);
chatAndRequestController.animateTo(1);
Navigator.of(context).pop();
// Navigator.of(contextProv.context).pop();
// SystemChannels.platform.invokeMethod('SystemNavigator.pop');
// Navigator.popUntil(
// context,
// ModalRoute.withName('/mainProfileScreen'),
// );
// Navigator.of(context)
// .popUntil(ModalRoute.withName('/mainProfileScreen'));
// Navigator.pushAndRemoveUntil(
// context,
// MaterialPageRoute(
// builder: (BuildContext context) => MainTabBarController(
// analytics: null,
// observer: null,
// latitude: 100.23423234,
// longitude: 12.324234234,
// isProfileBlocked: false,
// isVersionGood: true,
// ),
// ),
// (route) => true,
// );
} else {
var chatAndRequestProvider =
Provider.of<ChatAndRequestProvider>(context,
listen: false);
pageController.jumpToPage(1);
chatAndRequestController.animateTo(0);
Navigator.of(context).pop();
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ChatScreen(
appMode:
chatAndRequestProvider.chatsList.first[kAppMode],
peerId: chatAndRequestProvider.chatsList.first[kUserId],
peerAvatar: chatAndRequestProvider
.chatsList.first[kProfilePictureUrl],
peerName: chatAndRequestProvider
.chatsList.first[kNameAndSurname],
friendshipStatus: chatAndRequestProvider
.chatsList.first['friendsStatus'],
analytics: widget.analytics,
observer: widget.observer,
),
),
);
}
},
topText,
bottomText,
imageUrl,
),
);
}).then((val) {
if (_timer.isActive) {
_timer.cancel();
}
isDialogOpen = false;
});
}
I will try make my answer as general as possible in order to make it easier for others to follow along.
The problem in a nutshell is that you have a nested set of screens distributed between a set of pageviews, and you want to switch between the pageviews from an external event (The overlay in this case).
Below is an example:
TL;DR
I couldn't provide the full code since I don't have your full source code. But here is an example 😉
Note: This example uses Provider.
Sample Event Code
// Remove all the screens in the route
Navigator.of(context).popUntil((route) => route.isFirst); // If the screen is not the first replace the check
// Change the second pageview page
Provider.of<ChatSelectPageView>(context, listen: false).setPage(selectedSecondPageViewPage);
// In case it is required to add intermediate screens between the first and the second pageview it must be added here
// Change the main pageview page
_mainPageViewcontroller.animateToPage(1);
Second PageView
// Reads the page index present in the provider
int selectedPage = Provider.of<ChatSelectPageView>(context, listen: false).page;
// Changes to the cotroller page to the selected page
_pageController.jumpToPage(selectedPage);
ChatSelectPageView
class ChatSelectPageView extends ChangeNotifier {
int page = 0;
void setPage(int _page) {
page = _page;
// Depending on your implementation it might be better to remove this line to reduce the number of builds
notifyListeners();
}
}
TS;WM
In order to achieve the desired behavior, there is multiple ways to achieve it. If we want to stick to your implementation we will be a bit constrained. But in this case what I would suggest you do is to use some kind of global state management library such as provider, it can be done without any library but the state will get very messy very quickly.
As you mentioned above you tried Navigator.popUntil but it didn't work, I suspect the reason for this is that you are providing the wrong context. Since Navigator.**** relies on the context to work, i.e. to pop a screen you must provide its context. Or the route check is wrong.
This code is to be written in the external event in your case it will be written in the click listener of the overlay.
Use a state management solution such as Provider to pass the state to the descendants of the main Pageview down to the screens. This provider will be of type ChangeNotifierProvider. When the overlay is clicked, a flag will be set to be the desired pageview page index (I am speaking about the 2nd pageview). In your case this flag is used to select chats or requests.
After that is done you call Navigator.of(context).popUntil((route) => route.isFirst); assuming that the pageview is present on the first page of your app. In the case where it is not on that page, you will have to use Navigator.of(context).popUntil() with a custom logic.
After that we will have to navigate back to the 2nd pageview, or change the first pageview to be the 2nd page in your case. The second pageview will be already switched since we changed the flag in provider before.

How to avoid duplicate screen on top in Flutter

Giving that I have declared my routes in MaterialApp of my flutter application, now I am using
Navigator.pushNamed(context,ScreenA);
now on some user event I need to open ScreenA but only if ScreenA is not there already otherwise just update arguments in that ScreenA.
Have a look at this. You can await a result from all the pages you open from Screen A and use the values returned from these pages in Screen A once you pop back to it
You can check the current top screen and set your condition like below,
final newRouteName = "/NewRoute"; // Here add your route name
bool isNewRouteSameAsCurrent = false;
Navigator.popUntil(context, (route) {
if (route.settings.name == newRouteName) {
isNewRouteSameAsCurrent = true;
}
return true;
});
if (!isNewRouteSameAsCurrent) {
Navigator.pushNamed(context, newRouteName);
}
Refer.