android_job_scheduler 0.0.7

  • README.md
  • CHANGELOG.md
  • Example
  • Installing
  • Versions
  • 31

android_job_scheduler

Schedule Jobs using Android's JobScheduler API. This is very much Work in progress. Much of this Plugin is based on the android_alarm_manager Plugin.

Getting Started

Add the Dependency

Check the latest Version on pub.

Then, in your pubspec.yml:

dependencies:
  ...
  android_job_scheduler: ^0.0.7

Declare the JobScheduler Service in AndroidManifest.xml

In your project's android/app/src/main directory, open the AndroidManifest.xml and add the following to the <application /> block:

<service
    android:exported="false"
    android:permission="android.permission.BIND_JOB_SERVICE"
    android:name="io.gjg.androidjobscheduler.AndroidJobScheduler" />

Usage

import 'package:android_job_scheduler/android_job_scheduler.dart';

Installing Jobs

Periodic Jobs

// This MUST be a top level Function or a Static Class Member. It may not be a Class Method
// or a Closure. Inside this method, you don't have access to anything assuming a running
// Application.
void iRunPeriodically() {
    print('This gets run periodically by the Android JobScheduler API. '
          'Even when the App is not running. '
          'The Timings are not guaranteed to be exact by the Android OS, though.');
}

void main() {
    ...
    bool jobIsInstalled =
        // For every distinct Job you wish to create, set a new JobId.
        // Calling this Function twice with the same JobId will have no effect.
        await AndroidJobScheduler.scheduleEvery(
            const Duration(seconds: 10), iRunPeriodically, 42);
}

One-shot Jobs

You may schedule a Job to be run once, at some point in the Future. The Job will run even if the user closes the App in the meantime. If you reschedule the Job in the Period where it is not yet executed (by specifying the same JobId), the JobScheduler will restart the Job Execution Timeout, without executing the Job twice.

await AndroidJobScheduler.scheduleOnce(
  const Duration(seconds: 10), iRunOnlyOnceInTenSecs, 44
);

Canceling Jobs

void main() {
  ...
  // Cancel a unique Job
  await AndroidJobScheduler.cancelJob(42);
  // Cancel all Jobs scheduled by this Application
  await AndroidJobScheduler.cancelAllJobs();
}

Get Pending Jobs

void main() {
    ...
    await AndroidJobScheduler.scheduleEvery(
        const Duration(seconds: 10), iRunPeriodically, 42);
    await AndroidJobScheduler.scheduleEvery(
        const Duration(seconds: 10), iRunPeriodically, 43);
    
    final List<AndroidJobInfo> pendingJobs = await AndroidJobScheduler.getAllPendingJobs();
    print(pendingJobs.map((AndroidJobInfo i) => i.id).toList().join(", ")); // 42, 43
}

Reboot Persistence

You can specify that your job is persistent across Reboots. You'll need an additional Permission in your app's android/src/main/AndroidManifest.xml, though:

<manifest ...>
  ...
  
  <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
  
  <application>
    ...
  </application>
</manifest>

After you added the Permission, restart the test device or uninstall your application! Otherwise you will still get a permission error. This is because the JobScheduler holds a cache of which App Uids hold permission, which needs to be invalidated by one of these actions. After you got that sorted out, you can specify your Job to be persistent:

await AndroidJobScheduler.scheduleEvery(
   ...
   persistentAcrossReboots: true,
   ...
]);

Conditional Job Execution

Android's JobScheduler allows us to execute Jobs conditionally.

Only execute when Device is charging

await AndroidJobScheduler.scheduleEvery(
    const Duration(seconds: 10), iRunPeriodically, 42,
    constraints: [
          const RequiresCharging(),
]);

Only execute with a certain Network connectivity State

await AndroidJobScheduler.scheduleEvery(
    const Duration(seconds: 10), iRunPeriodically, 42,
    constraints: [
          const RequiredNetworkType(requiredType: RequiredNetworkType.NETWORK_TYPE_CELLULAR),
]);

Only execute if not on low Storage condition

await AndroidJobScheduler.scheduleEvery(
    const Duration(seconds: 10), iRunPeriodically, 42,
    constraints: [
          const RequiresStorageNotLow()
]);

Combinations

Of course, you can also combine Constraints.

// Only execute when the Device is charging,
// the Network connection is unmetered,
// and we have enough storage
await AndroidJobScheduler.scheduleEvery(
    const Duration(seconds: 10), downloadTonsOfData, 42,
    constraints: [
          const RequiredNetworkType(requiredType: RequiredNetworkType.NETWORK_TYPE_UNMETERED),
          const RequiresCharging(),
          const RequiresStorageNotLow()
]);

Using other Plugins from inside your callback

In order to use other Plugins from inside the Dart Callback, you'll need to enable the Scheduler to register with your Applications Main Plugin Registry. Do this by providing a custom Application Implementation in your Android Code:

Warning! Plugins generally have access to the FlutterActivity, in case they need access to any resources they provided by it. However, since we're running in a Job Context and not inside an Activity, the Activity will be null. Some Plugins might not expect this and crash on initialization. Plugins that fundamentally need Activities to function will not work when the main Application is not running. Your callbacks should still work if your Application is running, though.

import io.flutter.app.FlutterApplication;
import io.flutter.plugin.common.PluginRegistry;
import io.flutter.plugins.GeneratedPluginRegistrant;
import io.gjg.androidjobscheduler.AndroidJobScheduler;

public class MainApplication extends FlutterApplication implements PluginRegistry.PluginRegistrantCallback {

    @Override
    public void registerWith(PluginRegistry pluginRegistry) {
        GeneratedPluginRegistrant.registerWith(pluginRegistry);
    }

    @Override
    public void onCreate() {
        super.onCreate();
        AndroidJobScheduler.setPluginRegistrantCallback(this);
    }
}

Don't forget to reference your custom Application Implementation in AndroidManifest.xml.

...
<application
    android:name=".MainApplication">
    ...
</application>

FAQ

My Callbacks won't work with Plugins

One of the Plugins you're using is expecting to be run inside an Activity. Because there is no Activity when running in a Job Context, this will not work (see above).

I'm getting an Error telling be I need additional Permissions when scheduling a Job

E/flutter (25871): PlatformException(error, Error: requested job be persisted without holding RECEIVE_BOOT_COMPLETED permission., null)
E/flutter (25871): #0      StandardMethodCodec.decodeEnvelope (package:flutter/src/services/message_codecs.dart:547:7)

You specified persistentAcrossReboots: true when scheduling the Job, but you didn't declare the appropriate Permission in the Android Manifest. See above for what to add to the Manifest. If you did declare the Permission make sure you reboot your test device or uninstall your application, due to the reasons outlined above.jj

I'm getting an IllegalArgumentException when trying to Schedule anything

E/MethodChannel#plugins.gjg.io/android_job_scheduler(31525): Failed to handle method call
E/MethodChannel#plugins.gjg.io/android_job_scheduler(31525): java.lang.IllegalArgumentException: No such service ComponentInfo{io.gjg.testapp/io.gjg.androidjobscheduler.AndroidJobScheduler}

You forgot adding the JobScheduler Service to your AndroidManifest.xml. Please do this by following the steps outlined above.

For help getting started with Flutter, view our online documentation.

What is the difference to the android_alarm_manager plugin?

Most importantly it uses JobScheduler API instead of the AlarmManager API.

The main reason this plugin exists is that at the moment, android_alarm_manager is not working with the latest Flutter SDK Version. See this issue. If there is Interest from the Flutter Devs, I'll merge the changes relevant for fixing this issue back into android_alarm_manager.

Apart from that, the JobScheduler API offers a lot more Flexibility than the AlarmManager API for scheduling Jobs. For example, we can tell this API to only schedule Jobs when the Device is Charging, or when a unmetered Network Connection is available. However, none of these features are exposed here atm, though they could be pursued in the Future.

[0.0.1] - TODO: Add release date.

  • TODO: Describe initial release.

example/lib/main.dart

import 'dart:io';
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

import 'package:android_job_scheduler/android_job_scheduler.dart';

import 'package:path_provider/path_provider.dart';

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

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => new _MyAppState();
}

void jobSchedulerCallback() async {
  // Search Logcat for Yolo to see the the Callback firing
  // when the app is not running.
  print('Yolo executing');
  final file = await getCommonStateFile();
  if (!await file.exists()) {
    await file.writeAsString("0");
  } else {
    final contents = await file.readAsString();
    print("Contents: $contents");
    final timesCalled = int.parse(contents);
    await file.writeAsString("${timesCalled + 1}");
  }
}

Future<File> getCommonStateFile() async {
  final targetDir = await getApplicationDocumentsDirectory();
  return new File("${targetDir.path}/times_called.txt");
}

class _MyAppState extends State<MyApp> {
  List<int> _pendingJobs = new List<int>();
  int _timesCalled = 0;

  @override
  initState() {
    super.initState();
    initFileWatcher();
    updateCallBackTimesCalled();
    updatePendingJobs();
  }

  initFileWatcher() async {
    Timer.periodic(const Duration(seconds: 5), updateCallBackTimesCalled);
  }

  updateCallBackTimesCalled([Timer _]) async {
    final file = await getCommonStateFile();
    var timesCalled;
    if (!await file.exists()) {
      timesCalled = 0;
    } else {
      timesCalled = int.parse(await file.readAsString());
    }
    setState(() {
      _timesCalled = timesCalled;
    });
  }

  updatePendingJobs() async {
    final jobs =
        await AndroidJobScheduler.getAllPendingJobs();
    setState(() {
      _pendingJobs =
          jobs.map((AndroidJobInfo i) => i.id).toList();
    });
  }

  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      home: new Scaffold(
          appBar: new AppBar(
            title: new Text('Android Job Scheduler'),
          ),
          floatingActionButton: new FloatingActionButton(
              child: const Icon(Icons.refresh),
              onPressed: () {
                updatePendingJobs();
              }

          ),
          body: new Center(
              child: new Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              new Text(_pendingJobs.length == 0
                  ? 'No Pending Jobs.'
                  : 'Pending Jobs: ${_pendingJobs.join(', ')}'),
              new Text('Callback has been called '),
              new Text('$_timesCalled', textScaleFactor: 2.0),
              new Text('times.'),
              new Divider(),
              new Row(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: <Widget>[
                    new RaisedButton.icon(
                        onPressed: () async {
                          try {
                            await AndroidJobScheduler.scheduleEvery(
                                const Duration(seconds: 1),
                                42,
                                jobSchedulerCallback,
                                persistentAcrossReboots: true,
                                constraints: [
                                  const RequiresCharging(),
                                  const RequiresStorageNotLow()
                                ]
                            );
                          } finally {
                            updatePendingJobs();
                          }
                        },
                        icon: const Icon(Icons.check_box),
                        label: Text('Install Job')),
                    new Container(
                      width: 10.0,
                    ),
                    new RaisedButton.icon(
                        onPressed: () {
                          AndroidJobScheduler.cancelJob(42);
                          updatePendingJobs();
                        },
                        icon: const Icon(Icons.delete),
                        label: Text('Uninstall Job')),
                  ]),
              new Row(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: <Widget>[
                    new RaisedButton.icon(
                        onPressed: () async {
                          try {
                                await AndroidJobScheduler.scheduleEvery(
                                    const Duration(seconds: 10),
                                    43,
                                    jobSchedulerCallback,
                                persistentAcrossReboots: true);
                          } finally {
                            updatePendingJobs();
                          }
                        },
                        icon: const Icon(Icons.check_box),
                        label: Text('Install Geo Job')),
                    new Container(
                      width: 10.0,
                    ),
                    new RaisedButton.icon(
                        onPressed: () {
                          AndroidJobScheduler.cancelJob(43);
                          updatePendingJobs();
                        },
                        icon: const Icon(Icons.delete),
                        label: Text('Uninstall Geo Job')),
                  ]),
              new Row(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: <Widget>[
                    new RaisedButton.icon(
                        onPressed: () async {
                          try {
                            await AndroidJobScheduler.scheduleOnce(
                                const Duration(seconds: 10),
                                44,
                                jobSchedulerCallback,
                                persistentAcrossReboots: true);
                          } finally {
                            updatePendingJobs();
                          }
                        },
                        icon: const Icon(Icons.check_box),
                        label: Text('Schedule Once in 10s')),
                    new Container(
                      width: 10.0,
                    ),
                    new RaisedButton.icon(
                        onPressed: () {
                          AndroidJobScheduler.cancelJob(44);
                          updatePendingJobs();
                        },
                        icon: const Icon(Icons.delete),
                        label: Text('Unschedule')),
                  ]),
              new Divider(),
              new RaisedButton.icon(
                  onPressed: () async {
                    final file = await getCommonStateFile();
                    if (await file.exists()) {
                      await file.delete();
                      await updateCallBackTimesCalled();
                    }
                  },
                  icon: const Icon(Icons.fast_rewind),
                  label: const Text('Reset State')),
              new Container(
                padding: EdgeInsets.all(20.0),
                child: const Text(
                    'Close the App, wait a few secs, and see what happens!'),
              )
            ],
          ))),
    );
  }
}

Use this package as a library

1. Depend on it

Add this to your package's pubspec.yaml file:


dependencies:
  android_job_scheduler: ^0.0.7

2. Install it

You can install packages from the command line:

with pub:


$ pub get

with Flutter:


$ flutter packages get

Alternatively, your editor might support pub get or flutter packages get. Check the docs for your editor to learn more.

3. Import it

Now in your Dart code, you can use:


import 'package:android_job_scheduler/android_job_scheduler.dart';
  
Version Uploaded Documentation Archive
0.0.7 Jun 13, 2018 Go to the documentation of android_job_scheduler 0.0.7 Download android_job_scheduler 0.0.7 archive
0.0.6 Jun 13, 2018 Go to the documentation of android_job_scheduler 0.0.6 Download android_job_scheduler 0.0.6 archive
0.0.5 Jun 12, 2018 Go to the documentation of android_job_scheduler 0.0.5 Download android_job_scheduler 0.0.5 archive
0.0.4 Jun 12, 2018 Go to the documentation of android_job_scheduler 0.0.4 Download android_job_scheduler 0.0.4 archive
0.0.3 Jun 12, 2018 Go to the documentation of android_job_scheduler 0.0.3 Download android_job_scheduler 0.0.3 archive
0.0.2 Jun 12, 2018 Go to the documentation of android_job_scheduler 0.0.2 Download android_job_scheduler 0.0.2 archive
0.0.1 Jun 11, 2018 Go to the documentation of android_job_scheduler 0.0.1 Download android_job_scheduler 0.0.1 archive
Popularity:
Describes how popular the package is relative to other packages. [more]
63
Health:
Code health derived from static analysis. [more]
0
Maintenance:
Reflects how tidy and up-to-date the package is. [more]
0
Overall:
Weighted score of the above. [more]
31
Learn more about scoring.

The package version is not analyzed, because it does not support Dart 2. Until this is resolved, the package will receive a health and maintenance score of 0.

Analysis issues and suggestions

Support Dart 2 in pubspec.yaml.

The SDK constraint in pubspec.yaml doesn't allow the Dart 2.0.0 release. For information about upgrading it to be Dart 2 compatible, please see https://www.dartlang.org/dart-2#migration.

Dependencies

Package Constraint Resolved Available
Direct dependencies
Dart SDK >=1.19.0 <2.0.0