Currently Im adding guest mode feature so The App that I'm developing allow user to access some feature of the app without login (guest mode).
When the user clicked profile on bottom navigation, the app will do Intent and finish(java) or pushNamedAndRemoveUntil but it crashed.
Exception has occurred.
FlutterError (setState() or markNeedsBuild() called during build.
This _ModalScope widget cannot be marked as needing to build because the framework is already in the process of building widgets. A widget can be marked as needing to be built during the build phase only if one of its ancestors is currently building. This exception is allowed because the framework builds parent widgets before children, which means a dirty descendant will always be built. Otherwise, the framework might not visit this widget during this build phase.
The widget on which setState() or markNeedsBuild() was called was:
_ModalScope-[LabeledGlobalKey<_ModalScopeState>#bfd49]
The widget which was currently being built when the offending call was made was:
MainPage)
Widget moveToLogin() {
Navigator.of(context)
.pushNamedAndRemoveUntil('/sign-in', (Route<dynamic> route) => false);
return Container(
child: Text("Logging in..."),
);
}
Widget body() {
switch (currentIndex) {
case 0:
return HomePage();
break;
case 1:
return ActivityPage();
break;
case 2:
return ShoppingPage();
break;
case 3:
return ChatPage();
break;
case 4:
if (user.nama_customer == "guest") {
return moveToLogin();
} else {
return ProfilePage();
}
break;
default:
return HomePage();
}
}
create parameter first on statefulWidget
i've solved it with guest session. and the problem here was because of using "widget". it should be void
Related
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();
}
I am having a hard time navigating to a screen when a background FCM message receives. Currently, I am sending an FCM message with some data and when it gets received by a device then it calls this package that shows a calling notification, and when the user clicks on accept call option then I want to open my app and navigate to the desired screen. I use GetX for navigation and when I try to go to another screen it gives this exception:
I have tried almost everything to work this out but I am still unable to solve this problem.
my FirebaseMessaging.onBackgroundMessage Background handler which receives the notification also listens to user feedback on whether the call is accepted or rejected:
Future<void> firebaseMessagingBackgroundHandler(RemoteMessage message) async {
var incoming = <String, dynamic>{
'id': message.data['callerID'],
'nameCaller': message.data['callerName'],
'appName': 'Callkit',
'avatar': message.data['callerImage'],
'handle': '',
'type': 0,
'duration': 30000,
'extra': <String, dynamic>{'userId': '1a2b3c4d'},
'headers': <String, dynamic>{'apiKey': 'Abc#123!', 'platform': 'flutter'},
'android': <String, dynamic>{
'isCustomNotification': true,
'isShowLogo': false,
'ringtonePath': 'ringtone_default',
'backgroundColor': '#0955fa',
//'backgroundUrl': 'https://i.pravatar.cc/500',
'actionColor': '#4CAF50'
}};
await FlutterCallkitIncoming.showCallkitIncoming(incoming);
try {
FlutterCallkitIncoming.onEvent.listen((event) {
switch (event!.name) {
case CallEvent.ACTION_CALL_INCOMING:
print('INCOMING!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!');
break;
case CallEvent.ACTION_CALL_START:
// TODO: started an outgoing call
// TODO: show screen calling in Flutter
break;
case CallEvent.ACTION_CALL_ACCEPT:
print('accepted');
Get.offAll(()=> Incoming(
userName: null,
userImage: null,
userID: null,
userUsername: null));
break;
case CallEvent.ACTION_CALL_DECLINE:
print('rejected');
break;
case CallEvent.ACTION_CALL_ENDED:
// TODO: ended an incoming/outgoing call
break;
case CallEvent.ACTION_CALL_TIMEOUT:
// TODO: missed an incoming call
break;
case CallEvent.ACTION_CALL_CALLBACK:
// TODO: only Android - click action `Call back` from missed call notification
break;
case CallEvent.ACTION_CALL_TOGGLE_HOLD:
// TODO: only iOS
break;
case CallEvent.ACTION_CALL_TOGGLE_MUTE:
// TODO: only iOS
break;
case CallEvent.ACTION_CALL_TOGGLE_DMTF:
// TODO: only iOS
break;
case CallEvent.ACTION_CALL_TOGGLE_GROUP:
// TODO: only iOS
break;
case CallEvent.ACTION_CALL_TOGGLE_AUDIO_SESSION:
// TODO: only iOS
break;
}
print("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
});
} on Exception {}
}
Similar to the answer that Peter Koltai gave, your background handler is isolated from your application's context and so it's not possible to route to screens (which require a context) directly from your handler.
One possible solution is to implement an Android service using native code that communicates with Flutter via a MethodChannel and on the event that a call is accepted, you can navigate screens.
You can change your MaterialApp() with GetMaterialApp(), and check if you are using get.to(AnotherPage()), instead use get.to(()=>AnotherPage()).
i never tested it before but i think you should use wakelock to keep your screen awake
enter link description here
I am using christocracy's flutter_background_geolocation package to build a crowdsensing app. This app relies on the geofencing function of the aforementioned package quite heavily. In the main function, I have implemented a callback function that is as follows (partial code):
void _onGeofence(bg.GeofenceEvent event) async {
await showGeofenceNotification(flutterLocalNotificationsPlugin,
title: "Geofence", body: "$event", id: notification_id);
if (action == "ENTER") {
// update certain variables
BarometerService.startBarometerService();
BarometerService.floorChange.listen((floorChanges) {
// update floor
updateDatabase();
});
}
else if (action == "EXIT") {
// update certain variables
BarometerService.stopBarometerService();
}
updateDatabase();
setState(() {
// update UI
});
}
The code works perfectly when the app is open and in focus. However, when in background, the barometer service stops. The updateDatabase() function is also not carried out as my Firestore console doesn't get updated.
Here is the code for updating the database:
Future updateUserState(String matric, bool inLWN, bool inVaughan, String activity, int confidence, int floor) async {
return await userCollection.document(uid).setData({
'matric': matric,
'inLWN': inLWN,
'inVaughan': inVaughan,
'activity': activity,
'confidence': confidence,
'floor': floor,
});
}
And here is the code for BarometerService (which uses Flutter sensors plugin):
import 'package:sensors/sensors.dart';
static startBarometerService() {
Stream<BarometerEvent> barometer10Events = barometerEvents.throttle(Duration(seconds:PERIOD_SECONDS));
subscription = barometer10Events.listen(onBarometer);
streamController = new StreamController();
}
How do I make my services run even when app is closed or terminated? I have implemented the same code in my headless callback functions (except updating UI), but nothing besides updating my (local) variables and showing local flutter notifications is working.
Headless task for reference:
void headlessTask(bg.HeadlessEvent headlessEvent) async {
print('[BackgroundGeolocation HeadlessTask]: $headlessEvent');
switch(headlessEvent.name) {
case bg.Event.GEOFENCE:
bg.GeofenceEvent geofenceEvent = headlessEvent.event;
onHeadlessGeofence(geofenceEvent);
print('- [Headless] GeofenceEvent: $geofenceEvent');
break;
case bg.Event.ACTIVITYCHANGE:
bg.ActivityChangeEvent event = headlessEvent.event;
onHeadlessActivityChange(event);
print('- [Headless] ActivityChangeEvent: $event');
break;
}
}
onHeadlessGeofence is almost identical to the callback _onGeofence, besides the setState().
The full code can be found here
I'm experiencing the following issue: My Flutter app uses a GoogleMap. The map loads just fine initially. However, if I put the app into the background and resume a while later, the map stays blank. The Google logo still shows, like it happens when the API key isn't specified. My polygon overlay doesn't show up, either.
The behavior is not reliably repruducable. Sometimes, the map loads fine after the app had been in the background for hours, sometimes the map is blank after minutes. So far, I have only seen this behavior on Android.
There are no specific log outputs that indicate an error.
Any ideas how to fix/work around this?
I filed an issue with screenshot here: https://github.com/flutter/flutter/issues/40284
EDIT 1:
I was able to reproduce this with a GoogleMap as root widget and also without any polygon/feature overlay. Also, I found that wildly zooming in at some point 'reanimates' the map (suddenly the map becomes visible again). Is this maybe a known issue with the underlying Android Google Maps SDK?
EDIT 2:
I found that the map is still reacting (e.g. tap/gesture listeners still trigger). Also, the map isn't really empty, it just becomes translucent, so the screen displays whatever widget is behind the map.
I discovered that if you tap a marker or change the style the map re-renders
class TheWidgetThatHasTheMap with WidgetsBindingObserver {
//...your code
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
controller.setMapStyle("[]");
}
}
}
Not a solution to the core problem, but I was able to work around this bug by creating a fork of the plugins project and modifying GoogleMapController.java as follows:
#Override
public void onActivityResumed(Activity activity) {
if (disposed || activity.hashCode() != registrarActivityHashCode) {
return;
}
mapView.onResume();
// Workaround for https://github.com/flutter/flutter/issues/40284
// This apparently forces a re-render of the map.
if (googleMap != null) {
googleMap.setMapType(googleMap.getMapType());
}
}
Now, on every resume event, the map will be re-rendered.
I tried something & it seems to be working!
Step 01,
Implement WidgetsBindingObserver for related class's State class as follows,
i.e:
class MainScreenState extends State<MainScreen> with WidgetsBindingObserver {....
Step 02,
Override didChangeAppLifecycleState method
i.e:
#override
Future<void> didChangeAppLifecycleState(AppLifecycleState state) async {
super.didChangeAppLifecycleState(state);
switch (state) {
case AppLifecycleState.inactive:
print('appLifeCycleState inactive');
break;
case AppLifecycleState.resumed:
print('appLifeCycleState resumed');
break;
case AppLifecycleState.paused:
print('appLifeCycleState paused');
break;
case AppLifecycleState.detached:
print('appLifeCycleState detached');
break;
}
}
Step 03
add this for init state
WidgetsBinding.instance!.addObserver(this);
Step 04
Step 4 should be as follows
//onMapCreated method
void onMapCreated(GoogleMapController controller) {
controller.setMapStyle(Utils.mapStyles);
_controller.complete(controller);
}
// lifecycle
#override
Future<void> didChangeAppLifecycleState(AppLifecycleState state) async {
super.didChangeAppLifecycleState(state);
switch (state) {
case AppLifecycleState.inactive:
print('appLifeCycleState inactive');
break;
case AppLifecycleState.resumed:
**//Add These lines**
final GoogleMapController controller = await _controller.future;
onMapCreated(controller);
print('appLifeCycleState resumed');
break;
case AppLifecycleState.paused:
print('appLifeCycleState paused');
break;
case AppLifecycleState.detached:
print('appLifeCycleState detached');
break;
}
}
When dealing with a stateful widget, put the code below in your code as shown below
class MainScreenState extends State<MainScreen> with WidgetsBindingObserver
{....
#override
void initState() {
super.initState();
WidgetsBinding.instance!.addObserver(this);
... // Other codes here
}
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
mapController!.setMapStyle("[]");
}
}
}
Then you can add the code below in the state widget
Another simpler way to implement solution with setMapStyle within statefull widget of map widget. No need to change anything else:
Import flutter services:
import 'package:flutter/services.dart';
Code:
SystemChannels.lifecycle.setMessageHandler((msg) {
if (msg == AppLifecycleState.resumed.toString()) {
mapController.setMapStyle("[]");
}
});
"mapController" here is the instance of Google map controller you named somewhere in your code. In my case it is like this:
GoogleMapController _mapController;
GoogleMapController get mapController => _mapController;
if you facing this problem in 2022 also add this line above your class
class YourClass extends StatefulWidget with WidgetsBindingObserver
Completer<GoogleMapController> controller = Completer();
#override
void dispose() {
WidgetsBinding.instance!.removeObserver(this);
super.dispose();
}
#override
void didChangeAppLifecycleState(AppLifecycleState state) async {
super.didChangeAppLifecycleState(state);
print('\n\ndidChangeAppLifecycleState');
if (state == AppLifecycleState.resumed) {
final GoogleMapController controller1 = await controller.future;
controller1.setMapStyle('[]');
}
}
void initState() {
super.initState();
WidgetsBinding.instance!.addObserver(this);
}
Another temporary fix that doesn't required forking the plugins, building, etc.
Add a didChangeAppLifecycleState implemented via WidgetsBindingObserver to your Widget and make the GoogleMap widget rebuild with a state change.
In my case the map threw a black screen when called setState, the below solution solved my problem.
return SingleChildScrollView(
physics: const NeverScrollableScrollPhysics(),
child:SizedBox(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
child: //Your GoogleMap here,
),
);
I need to report the current location of the device using a flutter app. I need to do that continiuesly even when the app is closed.
I currently implemented it using background_fetch which does the job about every 15 minutes.
It works well when the app is open or minimized. But when the app is closed, it functions in Headless mode and doesn't work. the Exception is:
MissingPluginException(No implementation found for method getLocation on channel lyokone/location)
It seems that in Headless mode, not all the app is loaded in memory. I don't have any idea how to solve it.
Also I tried using an Isolate but I face with a new exception:
native function 'Window_sendPlatformMessage' (4 arguments) cannot be found.
Anybody knows how to solve these problems or have any new idea to do the location tracking?
Another option is use package https://github.com/Lyokone/flutterlocation https://github.com/Lyokone/flutterlocation/wiki/Background-Location-Updates
Support To get location updates even your app is closed, https://github.com/Lyokone/flutterlocation/wiki/Background-Location-Updates might have bugs
Also from transistorsoft and provide Android Headless Mod but need license
transistorsoft package https://github.com/transistorsoft/flutter_background_geolocation/wiki/Android-Headless-Mode
Android Headless Mod https://github.com/transistorsoft/flutter_background_geolocation/wiki/Android-Headless-Mode
code snippet
import 'package:flutter_background_geolocation/flutter_background_geolocation.dart' as bg;
void headlessTask(bg.HeadlessEvent headlessEvent) async {
print('[BackgroundGeolocation HeadlessTask]: $headlessEvent');
// Implement a 'case' for only those events you're interested in.
switch(headlessEvent.name) {
case bg.Event.TERMINATE:
bg.State state = headlessEvent.event;
print('- State: $state');
break;
case bg.Event.HEARTBEAT:
bg.HeartbeatEvent event = headlessEvent.event;
print('- HeartbeatEvent: $event');
break;
case bg.Event.LOCATION:
bg.Location location = headlessEvent.event;
print('- Location: $location');
break;
case bg.Event.MOTIONCHANGE:
bg.Location location = headlessEvent.event;
print('- Location: $location');
break;
case bg.Event.GEOFENCE:
bg.GeofenceEvent geofenceEvent = headlessEvent.event;
print('- GeofenceEvent: $geofenceEvent');
break;
case bg.Event.GEOFENCESCHANGE:
bg.GeofencesChangeEvent event = headlessEvent.event;
print('- GeofencesChangeEvent: $event');
break;
case bg.Event.SCHEDULE:
bg.State state = headlessEvent.event;
print('- State: $state');
break;
case bg.Event.ACTIVITYCHANGE:
bg.ActivityChangeEvent event = headlessEvent.event;
print('ActivityChangeEvent: $event');
break;
case bg.Event.HTTP:
bg.HttpEvent response = headlessEvent.event;
print('HttpEvent: $response');
break;
case bg.Event.POWERSAVECHANGE:
bool enabled = headlessEvent.event;
print('ProviderChangeEvent: $enabled');
break;
case bg.Event.CONNECTIVITYCHANGE:
bg.ConnectivityChangeEvent event = headlessEvent.event;
print('ConnectivityChangeEvent: $event');
break;
case bg.Event.ENABLEDCHANGE:
bool enabled = headlessEvent.event;
print('EnabledChangeEvent: $enabled');
break;
}
}
void main() {
runApp(HelloWorld());
// Register your headlessTask:
BackgroundGeolocation.registerHeadlessTask(headlessTask);
}