How to run dart code even when the app is terminated

99 views Asked by At

I have created an Android home screen widget with a button that executes Dart code (specifically, an API call) when clicked. Currently, this functionality only works when the app is in the background. I am looking to make it work even when the app is not in the background. Additionally, I prefer not to use any third-party plugins.

// Java code
public class NewAppWidget extends AppWidgetProvider {
    public static String valueToBeDisplayed = "temperature_value";
    private static boolean isAlarmStarted = false;
    public static int no_of_widget=0;
    PendingIntent pendingIntent;
    public static FlutterEngine flutterEngine;
    public static final String ACTION_AUTO_UPDATE = "AUTO_UPDATE";
    public static NewAppWidget appwidgetObj = new NewAppWidget();
    public static MethodChannel MethodChannel;
    static void updateAppWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetId){
        CharSequence widgetText = valueToBeDisplayed;
        RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.new_app_widget);
        views.setTextViewText(R.id.appwidget_text, widgetText);
        Intent buttonIntent = new Intent(context, NewAppWidget.class);
        buttonIntent.setAction("com.example.weather_widget2.BUTTON_CLICK");
        PendingIntent buttonPendingIntent = PendingIntent.getBroadcast(context, 0,
                buttonIntent, PendingIntent.FLAG_IMMUTABLE);
        views.setOnClickPendingIntent(R.id.buttonOk, buttonPendingIntent);
        int[] appWidgetIds = appWidgetManager.getAppWidgetIds(new ComponentName(context, NewAppWidget.class));
        for (int w : appWidgetIds) {
            appWidgetManager.updateAppWidget(w, views);
        }
        appWidgetManager.updateAppWidget(appWidgetId, views);
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
        int[] appWidgetIds = appWidgetManager.getAppWidgetIds(new ComponentName(context, NewAppWidget.class));
        if (intent != null && Objects.equals(intent.getAction(), ACTION_AUTO_UPDATE)) {
            System.out.println(valueToBeDisplayed + "----");
            System.out.println("action auto update has received");
            callFlutterMethod(context);
        }else{
            assert intent != null;
            if("com.example.weather_widget2.BUTTON_CLICK".equals(intent.getAction())){
                callFlutterMethod(context);
            }
        }

        for (int appWidgetId : appWidgetIds) {
            updateAppWidget(context, appWidgetManager, appWidgetId);
        }
        super.onReceive(context, intent);
    }
    @Override
    public void onEnabled(Context context) {
        no_of_widget++;
        System.out.print("called onEnabled at present no fo widgets are "+no_of_widget);
        super.onEnabled(context);
    }
    @Override
    public void onDeleted(Context context, int[] appWidgetIds) {
        System.out.println("called on 7777777777777 Deleted at present no fo widgets are "+no_of_widget);
        super.onDeleted(context, appWidgetIds);
    }

    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        if (!isAlarmStarted) {
            isAlarmStarted = true;
            scheduleAlarm(context);
        }
        for (int appWidgetId : appWidgetIds) {
            updateAppWidget(context, appWidgetManager, appWidgetId);
        }
    }

        private static void callFlutterMethod(Context context) {
        System.out.print("call method flutter method has been called");
        if(flutterEngine==null){
            System.out.print("again flutter cached++++++++++++++++++++++");
             flutterEngine = FlutterEngineCache.getInstance().get("my_engine_id");
        }
        if(flutterEngine!=null)
        {
            if(MethodChannel==null) {
            MethodChannel = new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), "my_channel123");
            }
            MethodChannel.setMethodCallHandler((call, result) -> {
                if (call.method.equals("InvokedWidget")){
                    String arg=call.argument("message");
                    valueToBeDisplayed=arg;
                    result.success("sample text has been sent from flutter to android");
                    System.out.println(valueToBeDisplayed);
                    AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
                    int[] appWidgetIds = appWidgetManager.getAppWidgetIds(new ComponentName(context, NewAppWidget.class));
                    appwidgetObj.onUpdate(context,appWidgetManager,appWidgetIds);
                }
            });
            MethodChannel.invokeMethod("requestLatestData", "");
        }
        else {
            System.out.print("flutter engine has been null");
        }
    }
    private void scheduleAlarm(Context context) {
        System.out.println("schedule alarm method is triggered");
        Intent intent = new Intent(context, NewAppWidget.class);
        intent.setAction(ACTION_AUTO_UPDATE);
        pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_IMMUTABLE);
        AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
        long intervalMillis = 1800000;
        alarmManager.setRepeating(AlarmManager.RTC, System.currentTimeMillis(), intervalMillis, pendingIntent);
    }
}

public class MainActivity extends FlutterActivity {

    @Override
    public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
        super.configureFlutterEngine(flutterEngine);
        FlutterEngineCache.getInstance().put("my_engine_id", flutterEngine);
    }
}


// Dart Code
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});
  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  static const channelName = 'my_channel123';
  late MethodChannel methodChannel;

  void configureChannel() {
    methodChannel = const MethodChannel(channelName);
    methodChannel.setMethodCallHandler(methodHandler);
  }

  List<dynamic> lst = [];
  Future<void> apiCall() async {
    apiData = await http.get(Uri.parse('http://api.open-meteo.com/v1/forecast?latitude=52.52&longitude=13.41&hourly=temperature_2m'));
    final body1 = apiData.body;
    final json = jsonDecode(body1);
    if (apiData.statusCode == 200) {
      lst = json['hourly']['temperature_2m'];
    }
    setState(() {});
  }

  Future<void> methodHandler(MethodCall call) async {
    switch (call.method) {
      case "requestLatestData":
        display();
        break;
      default:
        print('no method handler for method ${call.method}');
    }
  }

  @override
  void initState() {
    super.initState();
    apiCall();
    configureChannel();
  }

  dynamic apiData;
  var channel = const MethodChannel("ChannelName");
  dynamic result1;
  int num = 0;
  late String np;
  Future<void> display() async {
    np = lst[num].toString();
    var dt = DateTime.now();
    print("${dt.hour}:${dt.minute}:${dt.second}");
    await methodChannel.invokeMethod("InvokedWidget", {'message': np});
    setState(() {});
  }

  void updateData() {
    np = lst[num].toString();
    num++;
    setState(() {});
  }

  @override
  Widget build(BuildContext context) {
    return lst.isEmpty
        ? Container(
            color: Colors.blue,
            height: MediaQuery.of(context).size.height,
            width: MediaQuery.of(context).size.width,
          )
        : Scaffold(
            body: Center(
              child: Container(
                color: Colors.black,
                child: Center(
                  child: Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      const Text(
                        "Current Temperature",
                        textDirection: TextDirection.rtl,
                        textAlign: TextAlign.center,
                        style: TextStyle(color: Colors.white, fontSize: 48, fontWeight: FontWeight.bold),
                      ),
                      const SizedBox(
                        height: 30,
                      ),
                      Text(
                        lst[num].toString(),
                        style: const TextStyle(color: Colors.white, fontSize: 32, fontWeight: FontWeight.bold),
                      ),
                      const SizedBox(
                        height: 100,
                      ),
                      ElevatedButton(
                        onPressed: updateData,
                        child: const Text(
                          'Update Temperature',
                          style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
                        ),
                      )
                    ],
                  ),
                ),
              ),
            ),
          );
  }
}

1

There are 1 answers

1
Ahmad hassan On

You can use background_fetch or workmanager package for implementing this type of case in your Flutter application

Some basic example of both of them are mentioned below

  1. Background Fetch:

The background_fetch package is commonly used for tasks that need to be performed periodically in the background, like updating content or fetching data from an API.

void backgroundFetchHeadlessTask(String taskId) async {
  print('[BackgroundFetch] Headless event received.');
  // Perform your API call or other tasks here
  BackgroundFetch.finish(taskId);
}

void main() {
 runApp(MyApp());
 // Register to receive BackgroundFetch events after app is terminated.
 // Requires additional setup for iOS in AppDelegate.swift
 BackgroundFetch.registerHeadlessTask(backgroundFetchHeadlessTask);
}
  1. Workmanager

The workmanager package is used for background tasks that need more precise control or need to be executed under certain conditions, like network availability or charging status.

   void callbackDispatcher() {
     Workmanager().executeTask((task, inputData) {
     print("Native called background task: $task");
     // Perform your API call or other tasks here
     return Future.value(true);
    });
   }

   void main() {
     Workmanager().initialize(
      callbackDispatcher, // The top level function, aka 
      callbackDispatcher
      isInDebugMode: true // If enabled it will post a notification 
      whenever the task is running. Handy for debugging tasks
     );
    Workmanager().registerOneOffTask(
       "1", 
       "simpleTask", 
       initialDelay: Duration(seconds: 10),
       constraints: Constraints(
       networkType: NetworkType.connected,
      )
     );
    runApp(MyApp());
   }