How to open a standalone screen in flutter? - android

I am creating an alarm clock application, and I want to show a full screen page to allow the user to dismiss the alarm when it triggers. Thats all working well but the issue arises when I want to close that page.
What I have tried
Currently, when the alarm triggers, I am pushing that page onto the navigation stack to make it visible:
App.navigatorKey.currentState?.pushNamedAndRemoveUntil(
alarmNotificationRoute,
(route) {
return (route.settings.name != '/alarm-notification') ||
route.isFirst;
},
);
And then pop it when user presses "Dismiss":
if (App.navigatorKey.currentState?.canPop() ?? false) {
App.navigatorKey.currentState?.pop();
}
My App routing code:
class App extends StatefulWidget {
const App({super.key});
static final GlobalKey<NavigatorState> navigatorKey =
GlobalKey<NavigatorState>();
#override
State<App> createState() => _AppState();
}
class _AppState extends State<App> {
#override
Widget build(BuildContext context) {
return MaterialApp(
...
navigatorKey: App.navigatorKey,
initialRoute: '/',
onGenerateRoute: (settings) {
switch (settings.name) {
case '/':
return MaterialPageRoute(builder: (context) => const RootScreen());
case '/alarm-notification':
return MaterialPageRoute(
builder: (context) {
return AlarmNotificationScreen();
},
);
default:
assert(false, 'Page ${settings.name} not found');
return null;
}
},
);
}
}
Current behavior
Now when I pop, it returns to the default route of the flutter app '/', even when the alarm triggered while the app was closed.
Expected behavior
The behavior I want is as follows:
If the app was in the foreground when alarm triggered, pressing dismiss should go back to the last screen (this is already working as expected)
If the app was in the background or closed when alarm triggered, pressing dismiss should send the app to background
If android decides to show a Heads Up Notification instead of a full page intent. pressing dismiss should do nothing
Thoughts
I am thinking that the cleanest way to do so would be to launch a standalone page/activity, which we can just close when we press dismiss. Is there anyway to do such a thing? I am fine with it being an android-only solution.

There appears to be a minimize_app package that does the "close to background" behavior you want. From there it's simply a matter of tracking where the page was navigated from and using conditional logic.
A possible implementation:
import 'package:minimize_app/minimize_app.dart';
...
// Set this variable when the app is opened via the alarm trigger
if (appWasInBackground) {
MinimizeApp().minimizeApp();
} else if (App.navigatorKey.currentState?.canPop() ?? false) {
App.navigatorKey.currentState?.pop();
}

Related

Flutter: How to properly setup Pusher Beams when minimised or terminated?

Im struggling with this topic a few weeks. I registered on firebase console, pusher beams, everything setup as is written in documentation, I copied example project from flutter pusher_beams package so that when i send notification and app is opened, alert pop up and when is minimised, notification is shown in notification tray.
Problems:
When I tap on notification when app is minimised, i cannot check, which notification was tapped. I tried to add delay before that function but without help.
PusherBeams.instance.getInitialMessage is returning null, when i break there, but debugger dont stop on that breakpoint when i tap on notification when app is minimised.
I dont know how to handle notifications when app is killed. I found flutter_background_service, but when i set isForegroundMode to true, unhideable (if that word exists) notification is in notification tray and when i set isForegroundMode to false, when app is killed, that service is killed too. Another possibility is to use workmanager, but that can be called the fastest every 15 mins.
class _MyHomePageState extends State<MyHomePage> {
#override
initState() {
super.initState();
initPusherBeams();
}
initPusherBeams() async {
// Let's see our current interests
print(await PusherBeams.instance.getDeviceInterests());
// This is not intented to use in web
if (!kIsWeb) {
await PusherBeams.instance
.onInterestChanges((interests) => {
print('Interests: $interests')});
await PusherBeams.instance
.onMessageReceivedInTheForeground(_onMessageReceivedInTheForeground);
}
await _checkForInitialMessage();
}
Future<void> _checkForInitialMessage() async {
await Future.delayed(const Duration(seconds: 1));
final initialMessage = await PusherBeams.instance.getInitialMessage();
if (initialMessage != null) {
_showAlert('Initial Message Is:', initialMessage.toString());
}
}
void _onMessageReceivedInTheForeground(Map<Object?, Object?> data) {
_showAlert(data["title"].toString(), data["body"].toString());
}
void _showAlert(String title, String message) {
AlertDialog alert = AlertDialog(
title: Text(title), content: Text(message), actions: const []);
showDialog(
context: context,
builder: (BuildContext context) {
return alert;
},
);
}

How to detect hardware button press in flutter

I am building an app that starts by pressing the hardware button (Android/ios volume up button) for a long time, but I can't find any way to do it,
Is there any way to do it?
UPDATED ANSWER:
For iOS/Android:
check out this package:
Volume Watcher Package
For Desktop/Web:
You can use Focus widget or RawKeyboardListener widget to listen to keyboard events.
Here's a simple example:
#override
Widget build(BuildContext context) {
return Focus(
autofocus: true,
onKeyEvent: (node, event) {
if (event.physicalKey == PhysicalKeyboardKey.keyA) {
if (event is KeyDownEvent) {
// the user started pressing the key A
} else if (event is KeyRepeatEvent) {
// the user is pressing the key A
} else if (event is KeyUpEvent) {
// the user stopped pressing the key A
}
// if you handled the event (prevent propagating the events further)
return KeyEventResult.handled;
}
// otherwise return this (propagates the events further to be handled elsewhere)
return KeyEventResult.ignored;
},
child: Container(),
);
}
I'm using a macbook with touchbar so I couldn't confirm the volume up but you can replace PhysicalKeyboardKey.keyA with PhysicalKeyboardKey.audioVolumeUp.
Also, instead of using autofocus, you can use a FocusNode and pass it to the Focus widget to control when the widget has focus (i.e. when it should listen to events).
References:
a list of all physical keys (just go to the definition of PhysicalKeyboardKey on the IDE to see the list):
keyboard_key.dart
The rendered documentation page with a DartPad example:
PhysicalKeyboardKey class

How Can we get the lifecycle of StatefulWidget in flutter? I want to determine the lifecycle of widget on current screen not of complete app

How Can we get the lifecycle of StatefulWidget in flutter, if should pause if user is navigating to differnt screen and resume if user comes back to the screen.
didChangeAppLifecycleState giving lifecycle of app, not of the particular screen widget
Try FocusDetector, which is the closest to viewWillAppear/Disappear methods as seen on iOS.
FocusDetector(
onFocusLost: () {
logger.i(
'Focus Lost.'
'\nTriggered when either [onVisibilityLost] or [onForegroundLost] '
'is called.'
'\nEquivalent to onPause() on Android or viewDidDisappear() on iOS.',
);
},
onFocusGained: () {
logger.i(
'Focus Gained.'
'\nTriggered when either [onVisibilityGained] or [onForegroundGained] '
'is called.'
'\nEquivalent to onResume() on Android or viewDidAppear() on iOS.',
);
},
onVisibilityLost: () {
logger.i(
'Visibility Lost.'
'\nIt means the widget is no longer visible within your app.',
);
},
onVisibilityGained: () {
logger.i(
'Visibility Gained.'
'\nIt means the widget is now visible within your app.',
);
},
onForegroundLost: () {
logger.i(
'Foreground Lost.'
'\nIt means, for example, that the user sent your app to the background by opening '
'another app or turned off the device\'s screen while your '
'widget was visible.',
);
},
onForegroundGained: () {
logger.i(
'Foreground Gained.'
'\nIt means, for example, that the user switched back to your app or turned the '
'device\'s screen back on while your widget was visible.',
);
},
child: Container(),
);

how to disable user to go back after login in flutter

In this app when user click login page is navigate to homepage but when user press back button on home screen then page is navigate to login so this is not a right flow
I triend navigator.pushReplacement but when user press back button while on home screen app is close and go to background and when user open that app from background then instead showing home screen it show login screen so please give suggestions,
Here is my code
LoginScreen
Future<void> login(
String emailId, String password, String accessToken) async {
final dio = Dio(); // Provide a dio instance
String token = AppStrings.keyBearer + accessToken;
var customHeaders = {
AppStrings.authorization: token,
AppStrings.keyContentType: AppStrings.valueContentType
};
dio.options.headers.addAll(customHeaders);
final client = RestClient(dio);
await client
.loginUser(LoginUser(
deviceToken: AppStrings.valueDeviceToken,
lastLoginPlatform: AppStrings.valuePlatform))
.then((res) {
if(res.interests.isEmpty){
AppHelper.showToastMessage(
AppStrings.message_logged_in_successfully);
Navigator.of(context, rootNavigator: true).pop();
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => InterestsPage(
userAccesstoken: accessToken,
)));
}
else{
AppHelper.showToastMessage(
AppStrings.message_logged_in_successfully);
Navigator.of(context, rootNavigator: true).pop();
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => HomePage(
userAccesstoken: accessToken,
userInterests: res.interests
)));
}
}).catchError((Object obj) {
switch (obj.runtimeType) {
case DioError:
final res = (obj as DioError).response;
Navigator.of(context, rootNavigator: true).pop();
logger.e(res.statusMessage);
AppHelper.showToastMessage(AppStrings.message_something_went_wrong);
break;
}
});
}
I used
Navigator.of(context, rootNavigator: true).pop();
for close dialog box
I don't get any error but I want when user press back button on home screen app goes background and when user open that app from background show home screen not login screen
Show me where I made mistake in navigation and how to resolve it.
The method you are looking for is pushReplacement, and the way to go is:
Navigator.of(context).pop();
Navigator
.of(context)
.pushReplacement(
MaterialPageRoute(
builder: (BuildContext context) => InterestsPage(
userAccesstoken: accessToken,
)
)
)
This way, it will pop out of the alert message, and then replace all the previous pages with the one that you want.
However, the logic behing wether the login page needs to be displayed comes down to preference, and since I can't say how to do it without addicional code. I, for example, store the user on a local database after login. This way, even without a connection, there is a way to access the app.
You should use routes on your main file, using PushReplacement just works great there is no problem with it, you should decide where to navigate the user base on if the user logged in before or not, you can use something like share preferences to achieve this functionality. after login just save a pref that says user logged in and then on your splash screen navigate the user to correct screen

Navigator.push() loop forever

I'm making a LoginScreen, in LoginScreen i check data in database for know user logged or not for each times open app.
If user logged, the app will switched to HomeScreen.
I have a problem, i had logged in LoginScreen and then the app switched to HomeScreen. But my app's not standing in HomeScreen, it's continuing push new HomeScreen and looping this push action.
My code:
goToHomeIfAvailable() async {
// Go to HomeScreen if available
if (await this._databaseProvider.tokenTableIsEmpty() == false) {
print('Logged');
Navigator.push(
context,
MaterialPageRoute(builder: (context) => HomeScreen()),
);
}
}
#override
Widget build(BuildContext context) {
// In first times user open app=> create DB and go to HomeScreen if available
_databaseProvider.openOrCreate().then((_) async {
await goToHomeIfAvailable();
});
/* Return a widget bellow */
}
DatabaseProvider.dart:
class DatabaseProvider {
String _path = 'O2_DB.db';
Database _database;
Map _tableName = {'token': 'token_tbl'};
Future openOrCreate() async {
this._database = await openDatabase(this._path, version: 1,
onCreate: (Database db, version) async {
await db.execute('CREATE TABLE IF NOT EXISTS ' +
this._tableName['token'] +
' (token_id integer primary key autoincrement, token text)');
});
}
}
Build is called many times during the app life cycle - & its better to always put our logic outside build method. Its normal Behavior.
In your case - As build was called each time the method - goToHomeIfAvailable() was called hence multiple push.
Moving goToHomeIfAvailable() out of build to initState() will solve the issue.

Categories

Resources