Can't execute async method in Background with workmanager - flutter

In my app, I need to execute some tasks in the background (while app is not running in foreground). I'm able to execute some methods in the background but, I need to execute an async method in the background which I can't.
Here is a part of my code:
void main() {
runApp(MaterialApp(
home: Home(),
));
Workmanager.initialize(callbackDispatcher, isInDebugMode: true);
Workmanager.registerPeriodicTask("1", "simplePeriodicTask",
existingWorkPolicy: ExistingWorkPolicy.replace,
frequency: Duration(minutes: 15),
initialDelay:
Duration(seconds: 5),
constraints: Constraints(
networkType: NetworkType.connected,
));
}
void callbackDispatcher() {
Workmanager.executeTask((task, inputData) {
_HomeState().manager();//This is not Working
print('Background Services are Working!');//This is Working
return Future.value(true);
});
}
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
#override
void initState() {
login();
super.initState();
}
void manager() async {
if (account == null) {
await login();
await managerLocal();
managerDrive();
} else {
await managerLocal();
managerDrive();
}
}
.......
.......
}

You need to wait for your method to actually finish:
void callbackDispatcher() {
Workmanager.executeTask((task, inputData) async {
await _HomeState().manager();
print('Background Services are Working!');//This is Working
return true;
});
}
Your manager method should probably return a Future<void>, since it is async.
If you are unsure how to work with Future data, feel free to have a look here.

Related

Navigate from notification via beamer

I want to navigate to a specific page via beamer from a notification click.
In my main.dart I initialze my app and fcm. The class 'PushNotificationReceiver' should handle the notification logic.
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await PushNotificationReceiver.instance.initialize();
runApp(MultiProvider(providers: [
// Some of my providers
], builder: (context, _) => MyApp()));
}
class MyApp extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return MyAppState();
}
}
class MyAppState extends State<MyApp> {
#override
void initState() {
super.initState();
PushNotificationReceiver.instance.registerNotifications((route) => {
context.beamToNamed(route)
});
}
#override
Widget build(BuildContext context) {
return Consumer<ThemeProvider>(builder: (context, themeProvider, child) {
return MaterialApp.router(
routeInformationParser: BeamerParser(),
routerDelegate: _beamerDelegate,
backButtonDispatcher: BeamerBackButtonDispatcher(delegate: _beamerDelegate),
);
}
}
}
I implemented the functions to receive and show local notifications but to simplify it I only paste the code for the click (removed null checks as well).
class PushNotificationReceiver {
static PushNotificationReceiver _instance;
void Function(String route) navigateFunction;
static PushNotificationReceiver get instance {
if (_instance == null) {
_instance = new PushNotificationReceiver();
}
return _instance;
}
Future<void> initialize() async {
await Firebase.initializeApp();
}
void registerNotifications(void Function(String route) navigateFunction) {
this.navigateFunction = navigateFunction;
// Called the other functions to receive notifications, but excluded them for simplicity.
FirebaseMessaging.onMessageOpenedApp.listen((message) {
this.navigateFunction("/MyPage/${message.data["id"]}");
});
}
}
When I click on the notification I get the following error:
[ERROR:flutter/lib/ui/ui_dart_state.cc(198)] Unhandled Exception: 'package:beamer/src/beamer.dart': Failed assertion: line 40 pos 14: 'BeamerProvider.of(context) != null': There was no Router nor BeamerProvider in current context. If using MaterialApp.builder, wrap the MaterialApp.router in BeamerProvider to which you pass the same routerDelegate as to MaterialApp.router.
I tried it first without a function that I pass in and a GlobalKey in the main.dart with the same result.
Any suggestions?
Found the solution.
My first approach of a global key works if I wrap my MaterialApp.router in a Beamerprovider (like the error message suggested).
final GlobalKey myGlobalKey = GlobalKey();
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await PushNotificationReceiver.instance.initialize();
runApp(MultiProvider(providers: [
// Some of my providers
], builder: (context, _) => MyApp()));
}
class MyApp extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return MyAppState();
}
}
class MyAppState extends State<MyApp> {
#override
void initState() {
super.initState();
PushNotificationReceiver.instance.registerNotifications();
}
#override
Widget build(BuildContext context) {
return Consumer<ThemeProvider>(builder: (context, themeProvider, child) {
return BeamerProvider(
key: myGlobalKey,
routerDelegate: _beamerDelegate,
child: MaterialApp.router(
routeInformationParser: BeamerParser(),
routerDelegate: _beamerDelegate,
backButtonDispatcher: BeamerBackButtonDispatcher(
delegate: _beamerDelegate
)
)
);
}
}
}
That leads to my push notification receiver:
class PushNotificationReceiver {
static PushNotificationReceiver _instance;
static PushNotificationReceiver get instance {
if (_instance == null) {
_instance = new PushNotificationReceiver();
}
return _instance;
}
Future<void> initialize() async {
await Firebase.initializeApp();
}
void registerNotifications(void Function() {
// Called the other functions to receive notifications, but excluded them for simplicity.
FirebaseMessaging.onMessageOpenedApp.listen((message) {
myGlobalKey.currentContext.beamToNamed("/MyPage/${message.data["id"]}");
});
}
}
I hope this will help some others too.

how to await for network connectivity status in flutter

I have used connectivity_plus and internet_connection_checker packages to check the internet connectivity.
The problem occured is , the app works perfectly fine as expected when the app start's with internet on state. But when the app is opened with internet off, the dialog isn't shown !!
I assume this is happening because the build method is called before the stream of internet is listened.
Code :
class _HomePageState extends State<HomePage> {
late StreamSubscription subscription;
bool isDeviceConnected = false;
bool isAlertSet = false;
#override
void initState() {
getConnectivity();
super.initState();
}
getConnectivity() {
subscription = Connectivity().onConnectivityChanged.listen(
(ConnectivityResult result) async {
isDeviceConnected = await InternetConnectionChecker().hasConnection;
if (!isDeviceConnected && isAlertSet == false) {
showDialogBox();
setState(() {
isAlertSet = true;
});
}
},
);
}
#override
void dispose() {
subscription.cancel();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
...
);
}
showDialogBox() => showDialog(/* no internet dialog */)
Extending the question: Is it assured that this works for all the pages ?
if yes, how ?
if not , how to overcome this?
First of all you need to listen for internet connectivity in your app first screen which is probably app.dart
GlobalKey<NavigatorState> navigatorKey = GlobalKey();
final noInternet = NoInternetDialog();
class TestApp extends StatefulWidget {
#override
State<TestApp> createState() => _TestAppState();
}
class _TestAppState extends State<TestApp> {
#override
void initState() {
super.initState();
checkInternetConnectivity();
}
#override
Widget build(BuildContext context) {
return MaterialApp(...);
}
Future<void> checkInternetConnectivity() async {
Connectivity().onConnectivityChanged.getInternetStatus().listen((event)
{
if (event == InternetConnectionStatus.disconnected) {
if (!noInternet.isShowing) {
noInternet.showNoInternet();
}
}
});
}
}
Make the screen stateful in which you are calling MaterialApp and in initState of that class check for your internet connection, like above
You are saying how can I show dialog when internet connection changes for that you have to create a Generic class or extension which you can on connectivity change. You have to pass context to that dialogue using NavigatorKey
class NoInternetDialog {
bool _isShowing = false;
NoInternetDialog();
void dismiss() {
navigatorKey.currentState?.pop();
}
bool get isShowing => _isShowing;
set setIsShowing(bool value) {
_isShowing = value;
}
Future showNoInternet() {
return showDialog(
context: navigatorKey.currentState!.overlay!.context,
barrierDismissible: true,
barrierColor: Colors.white.withOpacity(0),
builder: (ctx) {
setIsShowing = true;
return AlertDialog(
elevation: 0,
backgroundColor: Colors.transparent,
insetPadding: EdgeInsets.all(3.0.h),
content: Container(...),
);
},
);
}
}
Use checkConnectivity to check current status. Only changes are exposed to the stream.
final connectivityResult = await Connectivity().checkConnectivity();

why flutter WorkManager plugin not working in background?

I am using WorkManager to make my flutter app run in background too, but it doesn't seems to work. I am using stream builder to fetch api and a audio plugin to play audio every time there is new entry but i also wanted this to work even when the app is closed so i used WorkManager . But it is not working, the app is only working when it is opened.
this is my code
class net{
static Stream<http.Response> getRandomNumberFact() async* {
yield* Stream.periodic(const Duration(seconds: 5), (_) {
return http.get(Uri.parse("https://script.google.com/macros/s/AKfycbwhbpF4ZxuMUcTZZvObAqvE1pAbEfPt7gZHRV1vVp8PuKt39-ouOm-kQJ1U1LtlEwV-/exec"));
}).asyncMap((event) async => await event);
}
}
void callbackDispatcher(){
Workmanager().executeTask((taskName, inputData) {
net.getRandomNumberFact();
return Future.value(true); });
}
void main() async {
WidgetsFlutterBinding.ensureInitialized();
Workmanager().initialize(
callbackDispatcher, // The top level function, aka callbackDispatcher
isInDebugMode: false // This should be false
);
runApp(MaterialApp(home: PeriodicRequester(),));
}
var current= 8;
#override
void initState() {
// super.initState();
Workmanager().registerPeriodicTask("one", "data",frequency: const Duration(minutes: 15));
}
class PeriodicRequester extends StatelessWidget {
#override
Widget build(BuildContext context) {
return StreamBuilder<http.Response>(
stream: net.getRandomNumberFact(),
builder: (context, snapshot) {
if(snapshot.hasData)
{
var data = json.decode(snapshot.data!.body);
var l = data.length;
if(l > current){
current++;
print("updated");
AssetsAudioPlayer.newPlayer().open(
Audio("assets/song.mp3"),
);
print(current);
}
return Scaffold()```

shared preference + firebase auth not working properly

Firebase Auth is working properly, I'm able to log in, Sign in but I want to preserve the state of the application. For state persistence, I'm using Share preferences.
I'm using sharedpreferences. I made a class and defined some keys and methods to fetch and set the data in the keys at the time of login and use it later in the app.
Please help me out with an easy approch how to do this.
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
class CurrentUser {
static SharedPreferences? mypreferences;
static const emailKey = 'emailKey';
static const passwordKey = 'paswordKey';
static const loginStatusKey = 'loginStatusKey';
static Future init() async {
mypreferences = await SharedPreferences.getInstance();
}
static Future setUserEmail(String? emailValue) async {
return await mypreferences?.setString(emailKey, emailValue!);
}
static Future setUserPassword(String? passwordValue) async {
return await mypreferences?.setString(passwordKey, passwordValue!);
}
static Future setLoginStatus(bool status) async {
return await mypreferences?.setBool(loginStatusKey, status);
}
static String? getUserEmail() {
return mypreferences?.getString(emailKey);
}
static String? getUserPassword() {
return mypreferences?.getString(passwordKey);
}
static bool? getUserLoginStatus(){
if(loginStatusKey==null){
mypreferences?.setBool(loginStatusKey, false);
}
return mypreferences?.getBool(loginStatusKey);
}
}```
// storing the value during login //
``` myLogIn() async {
try {
UserCredential myuser= await FirebaseAuth.instance
.signInWithEmailAndPassword(email: email, password: password);
// print(myuser);
// print(myuser.user?.email);
CurrentUser.setUserEmail(myuser.user?.email);
CurrentUser.setUserPassword(password);
CurrentUser.setLoginStatus(true);
Navigator.pushReplacementNamed(context, MyRoutes.homeRoute);
} on FirebaseAuthException catch (e) {
if (e.code == 'user-not-found') {
print('user is not registered');
ScaffoldMessenger.of(context).showSnackBar(
MySnackBar.showcustomSnackbar('user is not registered'));
} else if (e.code == 'wrong-password') {
print('wrong password');
ScaffoldMessenger.of(context)
.showSnackBar(MySnackBar.showcustomSnackbar('wrong password'));
}
}
}```
// using the store login status in main file to show login page and home page accordingly.
```import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
// import 'firebase_options.dart';
import './pages/login.dart';
import './pages/signup.dart';
import './pages/homepage.dart';
import './utils/my_theme.dart';
import './utils/my_routes.dart';
import './pages/forgot_password.dart';
import './utils/my_user.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
CurrentUser.init();
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
// const MyApp({ Key? key }) : super(key: key);
final Future<FirebaseApp> initializeMyFirebaseApp = Firebase.initializeApp();
bool? isLogin = CurrentUser.getUserLoginStatus();
#override
void initState() {
// TODO: implement initState
isLogin = CurrentUser.getUserLoginStatus();
super.initState();
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: initializeMyFirebaseApp,
builder: (context, snaphot) {
//Error checking
if (snaphot.hasError) {
print("Something went wrong!");
}
//If snapshot state is in waiting or so
if (snaphot.connectionState == ConnectionState.waiting) {
return Center(
child: CircularProgressIndicator(),
);
}
//else we will show the normal login page.
return MaterialApp(
theme: MyTheme.lightTheme(context),
//isUserLoggedIn !=null ? isUserLoggedIn ? Home() : SignUp() : SignIn(),
home:isLogin!=null ?
isLogin! ? HomePage() : Login() : Login(),
// Login(),
routes: {
// "/":(context) => Login(),
MyRoutes.loginRoute: (context) => Login(),
MyRoutes.signupRoute: (context) => SignUp(),
MyRoutes.homeRoute: (context) => HomePage(),
MyRoutes.forgotpasswordRoute: (context) => ForgotPassword(),
},
);
});
}
}
```

How to change the current mediaItem when the next mediaItem in the queue is played

I'm developing an iOS app in Flutter, using a package called audio_service.
I use AudioServiceBackground.serQueue() to set multiple MediaItem to a Queue.
In the UI part, I'm trying to use AudioService.currentMediaItemStream to display the information of the currently playing media item.
When the first song in the queue is finished, the second song will be played. However, the information on the current media item does not change.
How do I detect that the song playing in the Queue has changed?
class AudioServiceScreen extends StatefulWidget {
#override
_AudioServiceScreenState createState() => _AudioServiceScreenState();
}
class _AudioServiceScreenState extends State<AudioServiceScreen> {
#override
void initState() {
super.initState();
Future(() async {
await AudioService.connect();
await start();
});
}
#override
void dispose() {
Future(() async {
await AudioService.disconnect();
});
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: ///,
body: StreamBuilder<MediaItem?>(
stream: AudioService.currentMediaItemStream,
builder: (context, snapshot) {
final mediaItem = snapshot.data;
},
),
);
}
Future<dynamic> start() async {
final success = await AudioService.start(
backgroundTaskEntrypoint: _backgroundTaskEntrypoint,
);
if (success) {
await updateQueue();
}
}
Future<void> updateQueue() async {
final queue = await getMediaLibrary(); // get data from FireStore
await AudioService.updateQueue(queue);
}
}
void _backgroundTaskEntrypoint() {
AudioServiceBackground.run(() => AudioPlayerTask());
}
class AudioPlayerTask extends BackgroundAudioTask {
final AudioPlayer audioPlayer = AudioPlayer();
#override
Future<void> onStart(Map<String, dynamic>? params) async {
final session = await AudioSession.instance;
await session.configure(const AudioSessionConfiguration.speech());
await AudioServiceBackground.setState(
controls: [MediaControl.pause, MediaControl.stop],
playing: false,
processingState: AudioProcessingState.connecting,
);
}
#override
Future<void> onUpdateQueue(List<MediaItem> queue) async {
await AudioServiceBackground.setQueue(queue);
try {
await audioPlayer.setAudioSource(ConcatenatingAudioSource(
children:
queue.map((item) => AudioSource.uri(Uri.parse(item.id))).toList(),
));
} on Exception catch (e) {
await onStop();
}
}
}
Try read the docs and follow some of the example there. Because on "Background Code" section, there is "on Start" code. Maybe that code can help you out. I am sorry I cannot help you that much. I never try that package.