flutterdartbackgroundsharedpreferencesbackground-process

Flutter - Shared Preferences return null in background process?


I am using flutter_background_service. I need to process image when the app goes in the background. I need some values for the processing that I store in shared preferences before the processing.

        ///
    Future<void> initializeService() async {
      final service = FlutterBackgroundService();
      // Create notification channel for Android
      const AndroidNotificationChannel channel = AndroidNotificationChannel(
        notificationChannelId, // id
        'IMAGE STAMPING SERVICE', // title
        description: 'This channel is used for important notifications.',
        importance: Importance.high, // Set importance
      );
      final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
          FlutterLocalNotificationsPlugin();
      // Create the notification channel
      await flutterLocalNotificationsPlugin
          .resolvePlatformSpecificImplementation<
              AndroidFlutterLocalNotificationsPlugin>()
          ?.createNotificationChannel(channel);
      await service.configure(
        androidConfiguration: AndroidConfiguration(
          onStart: onStart,
          autoStart: true,
          isForegroundMode: true,
          notificationChannelId: notificationChannelId,
          initialNotificationTitle: 'Image Stamping Service',
          initialNotificationContent: 'Initializing',
          foregroundServiceNotificationId: notificationId,
        ),
        iosConfiguration: IosConfiguration(
          autoStart: true,
          onForeground: onStart,
        ),
      );
    }
        /// Function to initialize photo watcher
    void initializePhotoWatcher() async {
      try {
        // Initialize SharedPreferences
        SharedPreferences prefs = await SharedPreferences.getInstance();

        // Retrieve the value from SharedPreferences
        bool arePermissionsGranted = prefs.getBool('permissionsGranted') ?? false;

        if (arePermissionsGranted) {
          photoDirectory = Directory(AppConstants.directoryPath);

          if (await photoDirectory?.exists() ?? false) {
            final watcher = DirectoryWatcher(photoDirectory!.path);
            print("DirectoryWatcher created for path: ${photoDirectory!.path}");
            watcher.events.listen((event) {
              print("Event detected: ${event.toString()}");
              if (event.type == ChangeType.ADD &&
                  !event.path.contains("stamped_") &&
                  !event.path.contains(".pending")) {
                print("Scheduling processing for file: ${event.path}");
    <------------ Invoking Service Here ------------>
                final service = FlutterBackgroundService();
                service.invoke('processImage', {'imagePath': event.path});
              }
            });
          } else {
            print("Camera directory does not exist or is not accessible");
          }
        } else {
          // Request permissions and wait for them to be granted
          bool permissionsGranted = await requestPermissions();
          if (permissionsGranted) {
            // Permissions were granted, continue with initialization
            photoDirectory = Directory(AppConstants.directoryPath);
            // Rest of the code...
          } else {
            // Permissions were not granted, handle accordingly
            print("Need more permissions.");
          }
        }
      } catch (e) {
        print(e.toString());
      }
    }
        ///
    Future<void> onStart(ServiceInstance service) async {
      // DartPluginRegistrant.ensureInitialized();
      // final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
      //     FlutterLocalNotificationsPlugin();
      // Set up listeners for foreground and background state transitions
      if (service is AndroidServiceInstance) {
        service.on('processImage').listen((data) {
          final String imagePath = data?['imagePath'];
          service.setAsBackgroundService();
          // Rest of the code to process the image
          processImage(imagePath);
        });
        service.on('setAsForeground').listen((event) {
          service.setAsForegroundService();
        });
        service.on('setAsBackground').listen((event) {
          service.setAsBackgroundService();
        });
      }
      service.on('stopService').listen((event) {
        service.stopSelf();
      });
    }

Retrieving in this:

        void processImage(String imagePath) async {
      try {
        final File imageFile = File(imagePath);
        final SharedPreferences prefs = await SharedPreferences.getInstance();
<------- Reloading as suggested in other solutions -------->
        await prefs.reload();
        // Retrieve user preferences
    <------------ Everything takes the default value as the prefs return null ------------>
        int fontSize = prefs.getInt('fontSize') ?? AppConstants.fontSize;
        int scaledFontSize = fontSize * 2;
        final Color fontColor = _getFontColorFromString(prefs);
        // Retrieve text position
        String textPositionString =
            prefs.getString('textPlacement') ?? AppConstants.textPosition.name;
        TextPosition textPosition = TextPosition.values.firstWhere(
            (e) => e.name == textPositionString,
            orElse: () => AppConstants.textPosition // default value if not found
            );
        String text = prefs.getString('text') ?? AppConstants.text;
        // Check if the file exists and is not a temporary file
        if (await imageFile.exists() && !imageFile.path.contains('.pending-')) {
          // Call the function to add a stamp to the photo
          await addStampToPhoto(
            image: imageFile,
            fontSize: scaledFontSize,
            text: text,
            fontColor: fontColor,
            textPosition: textPosition,
            photoDirectoryPath: AppConstants.directoryPath,
          );
        } else {
          // Log and handle the situation, perhaps by retrying later
          print('File does not exist or is a temporary file: $imagePath');
        }
      } //
      catch (e) {
        print('Error in processing file $imagePath: $e');
    // Optionally implement a retry mechanism or other error handling
      }
    }

Solution

  • The core challenge lies in accessing SharedPreferences values across isolates when using flutter_background_service. This limitation arises from background services running in separate isolates for efficiency and security. So I suggest passing values as arguments. like

    service.invoke('processImage', {
      'imagePath': event.path,
      'fontSize': prefs.getInt('fontSize') ?? AppConstants.fontSize,
    });
    

    and onStart function

    Future<void> onStart(ServiceInstance service) async {
      service.on('processImage').listen((data) {
        final String imagePath = data?['imagePath'];
        final int fontSize = data?['fontSize'];
        processImage(imagePath, fontSize);
      });
    }