Last active
January 9, 2018 06:11
-
-
Save LouisCAD/ea7228360250b34d8da4668768018839 to your computer and use it in GitHub Desktop.
A fix for Firebase Job Dispatcher issue #6 (https://github.com/firebase/firebase-jobdispatcher-android/issues/6). You need to extend PersistedJobService instead of JobService for this to work, and call Jobs.schedulePresisted(tag)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import com.google.android.gms.gcm.GcmTaskService | |
import com.google.android.gms.gcm.TaskParams | |
import com.example.androidapp.jobs.Jobs | |
import com.example.androidapp.jobs.tagsOfScheduledJobs | |
import timber.log.Timber | |
/** | |
* See [this issue](https://github.com/firebase/firebase-jobdispatcher-android/issues/6). | |
*/ | |
class JobDispatcherReschedulerWorkaroundService : GcmTaskService() { | |
override fun onRunTask(taskParams: TaskParams?) = 0 | |
override fun onInitializeTasks() { | |
Timber.i("onInitializeTasks") | |
tagsOfScheduledJobs.forEach { tag -> | |
Jobs.schedulePersisted(tag) | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import android.content.Context | |
import com.firebase.jobdispatcher.* | |
import com.firebase.jobdispatcher.Constraint.* | |
import com.firebase.jobdispatcher.RetryStrategy.DEFAULT_EXPONENTIAL | |
import com.example.androidapp.illegal | |
import com.example.androidapp.extensions.setService | |
import com.example.androidapp.extensions.shouldReplaceCurrent | |
import com.example.androidapp.prefs.DefaultPrefs | |
import splitties.init.appCtx | |
import timber.log.Timber | |
object Jobs { | |
private val jobDispatcher = FirebaseJobDispatcher(GooglePlayDriver(appCtx)) | |
object Tags { | |
const val EXAMPLE_JOB_1 = "example_job1" | |
const val EXAMPLE_JOB_2 = "example_job2" | |
} | |
fun schedulePersisted(jobTag: String) = setupNewJob { | |
apply { | |
// Common to all the persisted jobs. | |
tag = jobTag | |
lifetime = Lifetime.FOREVER | |
shouldReplaceCurrent = true | |
trigger = Trigger.NOW | |
retryStrategy = DEFAULT_EXPONENTIAL | |
} | |
when (jobTag) { | |
Tags.EXAMPLE_JOB_1 -> { | |
setService<Example1JobService>() | |
setConstraints(ON_ANY_NETWORK) | |
} | |
Tags.EXAMPLE_JOB_2 -> { | |
setService<Example2JobService>() | |
val networkConstraint = if (NetworkPrefs.uploadOnWifiOnly) ON_UNMETERED_NETWORK else ON_ANY_NETWORK | |
if (NetworkPrefs.uploadOnlyWhenCharging) { | |
setConstraints(DEVICE_CHARGING, networkConstraint) | |
} else setConstraints(networkConstraint) | |
} | |
else -> illegal("Unknown job tag: $jobTag") | |
} | |
persistJobScheduledState(jobTag) | |
}.schedule() | |
fun cancel(jobTag: String) { | |
persistJobCancelled(jobTag) | |
val result = jobDispatcher.cancel(jobTag) | |
// region Error cases handling (throws in debug, reports in release) | |
if (result == FirebaseJobDispatcher.CANCEL_RESULT_SUCCESS) return Timber.d("Successfully cancelled job with tag: $jobTag") | |
val failure = when (result) { | |
FirebaseJobDispatcher.CANCEL_RESULT_NO_DRIVER_AVAILABLE -> "NO_DRIVER_AVAILABLE" | |
FirebaseJobDispatcher.CANCEL_RESULT_UNKNOWN_ERROR -> "UNKNOWN_ERROR" | |
else -> illegal("Unknown result: $result") | |
} | |
val e = RuntimeException("Job with tag $jobTag scheduling failed: $failure") | |
if (BuildConfig.DEBUG) throw e else Timber.wtf(e) | |
// endregion | |
} | |
private inline fun setupNewJob(config: Job.Builder.() -> Unit): Job { | |
return jobDispatcher.newJobBuilder().apply { config() }.build() | |
} | |
@Suppress("NOTHING_TO_INLINE") | |
private inline fun Job.schedule() { | |
val result = jobDispatcher.schedule(this) | |
// region Error cases handling (throws in debug, reports in release) | |
if (result == FirebaseJobDispatcher.SCHEDULE_RESULT_SUCCESS) return Timber.d("Successfully scheduled job with tag: $tag") | |
val failure = when (result) { | |
FirebaseJobDispatcher.SCHEDULE_RESULT_UNSUPPORTED_TRIGGER -> "UNSUPPORTED_TRIGGER" | |
FirebaseJobDispatcher.SCHEDULE_RESULT_BAD_SERVICE -> "BAD_SERVICE" | |
FirebaseJobDispatcher.SCHEDULE_RESULT_NO_DRIVER_AVAILABLE -> "NO_DRIVER_AVAILABLE" | |
FirebaseJobDispatcher.SCHEDULE_RESULT_UNKNOWN_ERROR -> "UNKNOWN_ERROR" | |
else -> illegal("Unknown result: $result") | |
} | |
val e = RuntimeException("Job with tag $tag scheduling failed: $failure") | |
if (BuildConfig.DEBUG) throw e else Timber.wtf(e) | |
// endregion | |
} | |
} | |
abstract class PersistedJobService : JobService() { | |
/** Call this method instead of `jobFinished(jobs, false)` */ | |
fun jobDone(job: JobParameters) { | |
if (stateOf(job.tag) == STATE_RUNNING) setState(job.tag, STATE_NOT_SCHEDULED) | |
jobFinished(job, false) | |
} | |
/** Call this method instead of `jobFinished(jobs, true)` */ | |
fun jobFailed(job: JobParameters) { | |
if (stateOf(job.tag) == STATE_RUNNING) setState(job.tag, STATE_SCHEDULED) | |
jobFinished(job, true) | |
} | |
abstract fun startJob(job: JobParameters): Boolean | |
abstract fun stopJob(job: JobParameters): Boolean | |
override final fun onStartJob(job: JobParameters): Boolean { | |
setState(job.tag, STATE_RUNNING) | |
return startJob(job) | |
} | |
override final fun onStopJob(job: JobParameters): Boolean { | |
val shouldBeRetried = stopJob(job) | |
if (shouldBeRetried || job.isRecurring) setState(job.tag, STATE_SCHEDULED) | |
else if (stateOf(job.tag) == STATE_RUNNING) setState(job.tag, STATE_NOT_SCHEDULED) | |
return shouldBeRetried | |
} | |
} | |
private fun persistJobScheduledState(jobTag: String) = setState(jobTag, STATE_SCHEDULED) | |
private fun persistJobCancelled(jobTag: String) = setState(jobTag, STATE_NOT_SCHEDULED) | |
private fun stateOf(jobTag: String) = jobsStates.getInt(jobTag, STATE_NOT_SCHEDULED) | |
private fun setState(jobTag: String, newState: Int) { | |
jobsStates.edit().putInt(jobTag, newState).apply() | |
} | |
/** Used to reschedule them */ | |
val tagsOfScheduledJobs: Set<String> | |
get() = jobsStates.all.filterNot { (_, state) -> | |
(state as Int) == STATE_NOT_SCHEDULED | |
}.keys | |
private val jobsStates = appCtx.getSharedPreferences("jobs_states", Context.MODE_PRIVATE)!! | |
private const val STATE_NOT_SCHEDULED = 0 | |
private const val STATE_SCHEDULED = 1 | |
private const val STATE_RUNNING = 2 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
fun illegal(errorMessage: String? = null): Nothing = throw IllegalStateException(errorMessage) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import splitties.preferences.DefaultPreferences | |
/** Property SharedPreferences. See [Splitties](https://github.com/LouisCAD/Splitties#preferences) | |
object NetworkPrefs : DefaultPreferences() { | |
var uploadOnWifiOnly by BoolPref("upload_on_unmetered_networks", true) | |
var uploadOnlyWhenCharging by BoolPref("upload_only_when_charging", false) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<manifest> | |
... | |
<application/> | |
... | |
<!-- region Firebase Job Dispatcher reschedule workaround --> | |
<service | |
android:name=".JobDispatcherReschedulerWorkaroundService" | |
android:permission="com.google.android.gms.permission.BIND_NETWORK_TASK_SERVICE"> | |
<intent-filter> | |
<action android:name="com.google.android.gms.gcm.ACTION_TASK_READY"/> | |
</intent-filter> | |
</service> | |
<!-- endregion --> | |
... | |
</application> | |
</manifest> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment