Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How update UI in background using workmanager? #559

Open
eserdeiro opened this issue May 25, 2024 · 4 comments
Open

How update UI in background using workmanager? #559

eserdeiro opened this issue May 25, 2024 · 4 comments
Labels
bug Something isn't working

Comments

@eserdeiro
Copy link

eserdeiro commented May 25, 2024

Hi, to put them in context
I am consuming an API, and I save the data to a local database (Hive), then I get this data using a Stream to the local database.
When I run Workmanager, when the app is minimized (in my case registerPeriodicTask), doing a get of the api and updating the data every 15', the data is updated in the DB but not in the UI (this happens when the app is in the background and you go back to the app -> foreground ).

I have already tried:

  • Use Provider, and notify listeners
  • Notify the stream again that there were changes
  • Use set state when returning the app to foreground
  • Use AutomaticKeepAliveClientMixin = true
  • Send updated data to other box in hive, and update my main box in foreground with the new data

But nothing seems to work. Any idea how to correct it?

This is a basic example of my screen

class TestView extends StatelessWidget {
  const TestView({super.key});

  @override
  Widget build(BuildContext context) {
    final localDatabaseRepository = LocalDatabaseRepository();

    return Scaffold(
      appBar: AppBar(
        title: const Text('Test'),
        actions: [
          IconButton(
            onPressed: () {},
            icon: const Icon(Icons.add),
          ),
        ],
      ),
      body: StreamBuilder<List<DataEntity>>(
        stream: localDatabaseRepository.loadCasesStream(),
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.waiting) {
            return const Center(child: CircularProgressIndicator());
          } else if (snapshot.hasError) {
            return Text('Error: ${snapshot.error}');
          } else {
            final dataSnapshot = snapshot.data;
            if (dataSnapshot != null && dataSnapshot.isNotEmpty) {
              return ListView.builder(
                itemCount: dataSnapshot.length,
                itemBuilder: (context, index) {
                  final data =
                      dataSnapshot[dataSnapshot.length - index - 1].caseStatus!;
                  return Text('${data.text}');
                },
              );
            } else {
              return const Center(
                child: Text(
                  'Empty',
                  textAlign: TextAlign.center,
                ),
              );
            }
          }
        },
      ),
    );
  }
}

This is a basic example of mi main

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  //await FirebaseConfig.init();
  await dotenv.load();
  await Hive.initFlutter();
  Hive
    ..registerAdapter(CaseEntityAdapter())
    ..registerAdapter(CaseStatusAdapter())
    ..registerAdapter(HistCaseStatusAdapter());
  await Workmanager().initialize(callbackDispatcher, isInDebugMode: true);
  if (!await Permission.notification.isPermanentlyDenied) {
    await Permission.notification.request();
    await LocalNotificationManager().initNotification();
  }
  runApp(const MainApp());
}

class MainApp extends StatefulWidget {
  const MainApp({super.key});

  @override
  State<MainApp> createState() => _MainAppState();
}

class _MainAppState extends State<MainApp> {

  @override
  Widget build(BuildContext context) {
    return FGBGNotifier(
      onEvent: (FGBGType event) async {
        if (event == FGBGType.foreground) {
          await Workmanager().cancelAll();
        } else {
          //FGBGType.background
          await Workmanager().registerPeriodicTask(
            task,
            task,
             
            backoffPolicy: BackoffPolicy.exponential,
            frequency: const Duration(minutes: 15),
            constraints: Constraints(
              requiresBatteryNotLow: false, //Only android
              requiresCharging: false, //Only android
              requiresStorageNotLow: false, //Only android
              networkType: NetworkType.connected,
            ),
          );
        }
      },
      child: GetMaterialApp(
        debugShowCheckedModeBanner: false,
        theme: AppTheme.getTheme(),
        initialRoute: AppRouter.home,
        getPages: AppRouter.routes,
        navigatorObservers: [
          RouterObserver(),
        ],
        localizationsDelegates: const [
          S.delegate,
          GlobalMaterialLocalizations.delegate,
          GlobalWidgetsLocalizations.delegate,
          GlobalCupertinoLocalizations.delegate,
        ],
        supportedLocales: S.delegate.supportedLocales,
      ),
    )
  }
}

// CallbackDispatcher needs to be a top-level function
// If u want change taskName, also change taskName in ios/Runner/AppDelegate.m
const task = 'be.tramckrijte.workmanagerExample.iOSBackgroundAppRefresh';
@pragma('vm:entry-point')
void callbackDispatcher() {
  Workmanager().executeTask((taskName, inputData) async {
    try {
      switch (taskName) {
        case task:
          WidgetsFlutterBinding.ensureInitialized();
          await dotenv.load();
          await LocalNotificationManager().initNotification();
          await Hive.initFlutter();
          Hive
            ..registerAdapter(TestEntityAdapter())
            ..registerAdapter(TestsStatusAdapter())
            ..registerAdapter(HistTestStatusAdapter());
          await MyManager().fetchCases();
          log('Excecute task ${DateTime.now()}');
        default:
      }
    } catch (e) {
      log('error workmanager $e');
    }
    return Future.value(true);
  });
}

Thanks you.

@eserdeiro eserdeiro added the bug Something isn't working label May 25, 2024
@vigin-hsa
Copy link

any solution for this?
@eserdeiro

@eserdeiro
Copy link
Author

any solution for this? @eserdeiro

Hello! I didn't find a solution, I chose to install the "restart-app" package and restart the application when there are changes and the application is in the background.

@SEGVeenstra
Copy link

I have the same question.
I was thinking if it would be possible to send like a message/notification to the UI to notify it of the change. I've read something about isolates being able to communicate via ports, and I believe this is using a separate isolate under the hood, so should we be able to use that to send messages?

@Ansis100
Copy link

Ansis100 commented Aug 2, 2024

Yes, this can be done via IsolateNameServer.

Register a port when starting the app:

      receivePort = ReceivePort();
      IsolateNameServer.registerPortWithName(
        receivePort!.sendPort,
        'isolate_port',
      );

Lookup the port from the worker:

    final port = IsolateNameServer.lookupPortByName('isolate_port');

Send data from the worker:

    port.send(data);

Listen on the main thread for new data from workers:

      receivePort!.listen((data) {
        // Update your UI data, for example:
        valueNotifier.value = data;
        notifyListeners();
      });

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

4 participants