Showing dialog when user loses internet connection in flutter - flutter

Is it possible to listen to the entire application in flutter and showing the dialog in case of loss of connection?

Yes it is possible. And you will need one package for this to work.
Internet Connection Checker
You can listen to the stream onConnectivityChanged from InternetConnectionChecker.
#override
void initState(){
super.initState();
var isDeviceConnected = false;
var subscription = Connectivity().onConnectivityChanged.listen((ConnectivityResult result) async {
if(result != ConnectivityResult.none) {
isDeviceConnected = await InternetConnectionChecker().hasConnection;
if(!isDeviceConnected){
showDialog(
context,
// Your Dialog Here
);
}
}
});
}
Some things to consider here.
showDialog() needs a context, so put all of this code on the main
widget with MaterialApp.
You need to dismiss the dialog by yourself. You wont know if there is a dialog up or not at all. So, do it in your own discretion. Try not to pop() the MaterialApp itself.

Related

Flutter use context when handling dynamic link from app closed

I see this weird behaviour when handling dynamic links. What I want to do is that when coming from a link containing matchId parameter I want to clean up the navigation stack, put up the AvailableMatches page and after the MatchDetails page and finally show a modal. This is the code I use in the link handler
FirebaseDynamicLinks.instance.onLink(
onSuccess: (PendingDynamicLinkData dynamicLink) async {
final Uri deepLink = dynamicLink?.link;
if (deepLink != null) {
handleLink(deepLink);
});
Future<void> handleLink(Uri deepLink) async {
var context = navigatorKey.currentContext;
var matchId = deepLink.queryParameters["match_id"];
Navigator.of(context).pushNamedAndRemoveUntil(
AvailableMatches.routeName,
(Route<dynamic> route) => false
);
Navigator.of(context).pushNamed(MatchDetails.routeName,
arguments: ScreenArguments(
matchId, false)
);
await showModalBottomSheet(context: context, builder: (context) => Text("done"));
}
If the app is already open this works fine. If the app is starting from this link I have the following initState in the first StatefulWidget
void initState() {
super.initState();
initDynamicLinks();
loadData(context);
}
Future<void> loadData(BuildContext context) async {
final PendingDynamicLinkData data = await FirebaseDynamicLinks.instance.getInitialLink();
handleLink(data.link);
}
In this case the handeLink method works until the latest model. The pages are pushed correctly on the stack however the last model doesn't show up.
I am adding prints and things like that but it seems that this line never gets executed. There is no crash or exception. It just gets ignored

How to handle redirects from initState in Flutter

I am using the plugin flutter_local_notifications to show a user notifications on the app. I have managed to show the notifications on the app and the user can click on the notifications, the user will be redirected to see all pending items from the notification in a notification page. I have set the function to detect the onclick on initstate. The whole function and method to show notification I have implemented it on the homescreen.
#override
void initState() {
super.initState();
initPlatformState();
getNotifications();
NotificationApi.init(initScheduled: true);
init();
_configureSelectNotificationSubject(); //function to redirect to Notifications page
}
which goes to
void _configureSelectNotificationSubject() {
print("clicked is the notification");
NotificationApi.onNotifications.stream.listen((String? payload) async {
await Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (context) => WANotifications(current: current)),
(Route<dynamic> route) =>
true);
});
}
The challenge am facing with this implementation is that when a user clicks to go to the home screen , the user gets redirected automatically to the notifications page from the home screen without his/her consent. The redirect should only occur when they click on the notifications.
How can I set the redirect to only occur when the user clicks the notification only and not when they click to go to home screen
You can achieve this with the help of didNotificationLaunchApp.
bool get didNotificationLaunchApp =>
notificationAppLaunchDetails?.didNotificationLaunchApp ?? false;
// Use here,
if (notificationAppLaunchDetails?.didNotificationLaunchApp ?? false) {
selectedNotificationPayload = notificationAppLaunchDetails!.payload;
initialRoute = SecondPage.routeName;
}
Pl check full example here : https://pub.dev/packages/flutter_local_notifications/example
For non main file :
#override
void initState() {
super.initState();
localNotification(); // call below localNotification() here.
}
localNotification() async {
final NotificationAppLaunchDetails? notificationAppLaunchDetails =
await flutterLocalNotificationsPlugin!.getNotificationAppLaunchDetails();
if (notificationAppLaunchDetails?.didNotificationLaunchApp ?? false) {
// redirect to new screen if true.
}
}

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.

didChangeAppLifecycleState doesn't work as expected

I hope I understand how didChangeAppLifecycleState worked correctly.
I have page A and page B . When I click the back device button from page B ( Navigator.of(context).pop(); ), I expect didChangeAppLifecycleState in pageA will get called, but it doesn't.
PageA
class _ABCState extends State<ABCrList> with WidgetsBindingObserver {
#override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
....
}
#override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
setState(() {
print(...);
});
}else{
print(state.toString());
}
}
....
This is the initState in pageA. The function used to call backend service.
#override
void initState() {
super.initState();
_bloc.getList(context); // return list and populate to ListView
});
}
The way you're thinking it is Android's way where onResume works, but in Flutter, things don't happen this way.
Generally, this gets called when the system puts the app in the background or returns the app to the foreground.
There are mainly 4 states for it:
resumed: The application is visible and responding to user input.
inactive: The application is in an inactive state and is not receiving user input.
paused: The application is not currently visible to the user, not responding user input, and running in the background.
detached: The application is still hosted on a flutter engine but is detached from any host views.
Edit:
When you're navigating to PageB from PageA, use something like:
Navigator.pushNamed(context, "/pageB").then((flag) {
if (flag) {
// you're back from PageB, perform your function here
setState(() {}); // you may need to call this if you want to update UI
}
});
And from PageB, you'll can use
Navigator.pop(context, true);

Streambuilder only fire once

I am having a problem where my stream builder is only firing once.
I am trying to configure my bottomNavigationBar to be of a different colour based on the theme selected by the user.
To do this, I have a page whereby the user can decide whether to use the light theme or dark theme. This is saved into the device while shared preferences and then using async, i will stream the current value into my bottomNavigationBar.
The problem occurs when i use a stream builder to create two if statement. Stating that if the value returned from the stream is 0, i will show a "light mode" bottom navigation bar. Else if its 1, i will show a dark theme.
All is well when i run the program for the first time. However upon navigation into the settings page and changing the user preference, the stream builder will not load again. Here are some snapshots of my code
I have tried removing the dispose method whereby the stream will close. However that didn't solve the problem.
The Stream Builder
class mainPagev2 extends StatefulWidget {
#override
State<StatefulWidget> createState() {
// TODO: implement createState
return _mainPageV2();
}
}
class _mainPageV2 extends State<mainPagev2>
with SingleTickerProviderStateMixin {
// TabController _tabController;
StreamController<int> streamController = new StreamController.broadcast();
#override
void initState() {
super.initState();
// _tabController = TabController(vsync: this, length: _pageList.length);
Stream<int> stream = new Stream.fromFuture(readCurrentTheme());
streamController.addStream(stream);
}
#override
void dispose() {
// _tabController.dispose();
super.dispose();
}
String currentColor = "#ab3334";
#override
Widget build(BuildContext context) {
// TODO: implement build
return StreamBuilder(
stream: streamController.stream,
builder: (context, asyncSnapshot) {
print(asyncSnapshot.data.toString() + "WHssssAT IS THIS");
if (asyncSnapshot.hasData) {
print(asyncSnapshot.error);
if (asyncSnapshot.data == 0) {
//Return light themed Container
currentColor = "#ffffff";
return ThemeContainer(color: currentColor );
} else {
currentColor = "#101424";
//Return dark themed Container
return ThemeContainer(color: currentColor );
}
} else {
//return dark themed
return ThemeContainer(color:currentColor);
}
},
);
//
}
}
Async Code to retrieve the value stored
Future<int> readCurrentTheme() async {
final prefs = await SharedPreferences.getInstance();
final key = 'themeMode';
final value = prefs.getInt(key) ?? 0;
print('read: $value LOOK AT THISSS');
return value;
}
It is expected that the stream builder will fire whenever the value stored is changed!
I don't see in your code a way to read data from SharedPreferences when the value stored is changed. You are effectively reading it once, so the StreamBuilder is only firering once. That makes sense.
To be able to do what you want, you have to use something to tell you widget that a state has changed elsewhere in the application. There a multiple ways to achieve this and I won't make the choice for you as it would be opinion based, so you can check thing like BloC, Provider, ScopedModel, InheritedWidget