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

Clarifying Background Task Scheduling in Flutter for iOS: Custom Tasks and Device Charging Status #374

Closed
AvishkaG opened this issue Jun 9, 2024 · 2 comments
Labels

Comments

@AvishkaG
Copy link

AvishkaG commented Jun 9, 2024

Your Environment

  • Plugin version: 1.3.4
  • Platform: iOS
  • OS version: Sanoma 14.4.1 (23E224)
  • Device manufacturer / model: iPhone 8+
  • Flutter info (flutter info, flutter doctor):
    [✓] Flutter (Channel stable, 3.19.3, on macOS 14.4.1 23E224 darwin-arm64, locale en-US)
    [✓] Android toolchain - develop for Android devices (Android SDK version 34.0.0)
    [✓] Xcode - develop for iOS and macOS (Xcode 15.0.1)
    [✓] Chrome - develop for the web
    [✓] Android Studio (version 2022.3)
    [✓] IntelliJ IDEA Community Edition (version 2023.2.1)
    [✓] VS Code (version 1.90.0)
    [✓] Connected device (4 available)
    [✓] Network resources

I have two issues.

  1. Do I have to use scheduleTask in order to run a background task periodically in iOS anyhow? It means, Do I have to add a custom task for that or can I use the same fetch task id in the scheduleTask as well like below?
      BackgroundFetch.scheduleTask(
        TaskConfig(
          taskId: 'com.transistorsoft.fetch',
          delay: 5000,
          periodic: true,
        ),
      );
  1. As per the documentation isn't there a way to run periodic background tasks in iOS when the device is unplugged (not charging) ?

main.dart

import 'dart:async';
import 'dart:developer';
import 'dart:io';
import 'package:cloud_brink_app/core/network/native_utils.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

import 'package:background_fetch/background_fetch.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';

import 'package:cloud_brink_app/config/themes/bloc/theme_bloc.dart';
import 'package:cloud_brink_app/views/login/ui/bloc/login_bloc.dart';
import 'package:cloud_brink_app/config/routes/app_router.dart';
import 'package:cloud_brink_app/config/themes/app_themes.dart';
import 'package:flutter_native_splash/flutter_native_splash.dart';
import 'injection_container.dart' as dependency_injection;
import 'package:cloud_brink_app/injection_container.dart';

@pragma('vm:entry-point')
void backgroundFetchHeadlessTask(HeadlessTask task) async {
  var taskId = task.taskId;
  bool isTimeout = task.timeout;
  if (isTimeout) {
    // This task has exceeded its allowed running-time.
    BackgroundFetch.finish(taskId);
    return;
  }
  // Perform your background task here
  await executeDPACheck();
  BackgroundFetch.finish(taskId);
}

Future<void> main() async {
  WidgetsBinding widgetsBinding = WidgetsFlutterBinding.ensureInitialized();
  FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding);
  await dependency_injection.init();
  await dotenv.load(fileName: 'env/.env');

  if (Platform.isAndroid) {
    await AndroidInAppWebViewController.setWebContentsDebuggingEnabled(true);
    var swAvailable = await AndroidWebViewFeature.isFeatureSupported(
        AndroidWebViewFeature.SERVICE_WORKER_BASIC_USAGE);
    var swInterceptAvailable = await AndroidWebViewFeature.isFeatureSupported(
        AndroidWebViewFeature.SERVICE_WORKER_SHOULD_INTERCEPT_REQUEST);

    if (swAvailable && swInterceptAvailable) {
      AndroidServiceWorkerController serviceWorkerController =
          AndroidServiceWorkerController.instance();

      await serviceWorkerController
          .setServiceWorkerClient(AndroidServiceWorkerClient(
        shouldInterceptRequest: (request) async {
          log(request.toString());
          return null;
        },
      ));
    }
  }

  SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]).then(
    (value) => runApp(
      MultiBlocProvider(
        providers: [
          BlocProvider(create: (_) => serviceLocator<LoginBloc>()),
          BlocProvider(create: (_) => serviceLocator<ThemeBloc>())
        ],
        child: const BrinkApp(),
      ),
    ),
  );

  BackgroundFetch.registerHeadlessTask(backgroundFetchHeadlessTask);
}

class BrinkApp extends StatefulWidget {
  const BrinkApp({Key? key}) : super(key: key);

  @override
  State<BrinkApp> createState() => _BrinkAppState();
}

class _BrinkAppState extends State<BrinkApp> {
  final appRouter = AppRouter();
  late DateTime currentTime;
  Timer? verifyAgentStateTimer;

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addPostFrameCallback((_) {
      configureBackgroundFetch();
      BlocProvider.of<ThemeBloc>(context).add(SetInitialThemeEvent());
      BlocProvider.of<LoginBloc>(context)
          .add(const CheckUserInfo(shouldStopAgent: true));
    });
  }

  Future<void> configureBackgroundFetch() async {
    await executeDPACheck();
    BackgroundFetch.configure(
      BackgroundFetchConfig(
        minimumFetchInterval: 15,
        stopOnTerminate: false,
        startOnBoot: true,
        enableHeadless: true,
        requiresBatteryNotLow: false,
        requiresCharging: false,
        requiresStorageNotLow: false,
        requiresDeviceIdle: false,
        requiredNetworkType: NetworkType.ANY,
      ),
      onBackgroundFetch,
      onBackgroundFetchTimeout,
    );
  }

  void onBackgroundFetch(String taskId) async {
    print("[BackgroundFetch] Event received: $taskId");
    await executeDPACheck();
    BackgroundFetch.finish(taskId);
  }

  void onBackgroundFetchTimeout(String taskId) async {
    print("[BackgroundFetch] TIMEOUT: $taskId");
    BackgroundFetch.finish(taskId);
  }

  @override
  Widget build(BuildContext context) {
    return BlocConsumer<LoginBloc, LoginState>(listener: (context, state) {
      verifyAgentStateTimer?.cancel();
      if (state is VerifyAgentState) {
        FlutterNativeSplash.remove();
        BlocProvider.of<LoginBloc>(context)
            .add(ScreenChangeToLogin(shouldStopAgent: state.shouldStopAgent));
      } else if (state is SessionAlive) {
        FlutterNativeSplash.remove();
        BlocProvider.of<LoginBloc>(context).add(ScreenChangeToHome(
            isRecoverableFromFault: state.isRecoverableFromFault));
      }
    }, builder: (context, state) {
      return BlocBuilder<ThemeBloc, ThemeState>(builder: (context, themeState) {
        ThemeBloc.setSystemOverlayStyle(themeState);
        return MaterialApp.router(
          debugShowCheckedModeBanner: false,
          title: 'CloudBrink',
          theme: AppTheme.lightTheme(context),
          darkTheme: AppTheme.darkTheme(context),
          themeMode: ThemeBloc.themeMode(themeState),
          localizationsDelegates: const [
            AppLocalizations.delegate,
            GlobalMaterialLocalizations.delegate,
            GlobalWidgetsLocalizations.delegate,
            GlobalCupertinoLocalizations.delegate,
          ],
          supportedLocales: const [
            Locale('en', ''), // English
            Locale('es', ''), // Spanish
          ],
          routerConfig: appRouter.router,
        );
      });
    });
  }
}

Copy link

This issue is stale because it has been open for 30 days with no activity.

@github-actions github-actions bot added the stale label Jul 10, 2024
Copy link

This issue was closed because it has been inactive for 14 days since being marked as stale.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant