Coder Social home page Coder Social logo

evilthreads669966 / bootlaces Goto Github PK

View Code? Open in Web Editor NEW
35.0 3.0 4.0 6.08 MB

A Kotlin work manager library for Android with progress notifications and Hilt support.

License: Apache License 2.0

Kotlin 100.00%
android-library android-background-sevices android-service foreground-service workmanager-kotlin hilt kotlin-android kotlin-library

bootlaces's Introduction

Release  API  Android Arsenal  Awesome Kotlin Badge

Boot Laces

A kotlin work manager library for Android that includes notifications and Hilt support.

User Instructions

  1. Add the JitPack repository to your project's build.gradle
allprojects {
	repositories {
		...
		maven { url 'https://jitpack.io' }
	}
}
  1. Add the kapt and hilt plugins to the top of your app's build.gradle file
apply plugin: 'kotlin-kapt'
apply plugin: 'dagger.hilt.android.plugin'
  1. Add the dependencies for boot laces & hilt to your app's build.gradle
dependencies {
        implementation 'com.github.evilthreads669966:bootlaces:10.0.2'
        implementation "com.google.dagger:hilt-android:2.36"
        kapt "com.google.dagger:hilt-android-compiler:2.36"
}
  1. Add the Hilt plugin to your project's build.gradle dependencies
dependencies {
    ...
    classpath "com.google.dagger:hilt-android-gradle-plugin:2.36"
}
  1. Annotate your subclass of Application class
@HiltAndroidApp
class App: Application()
  1. Add name of your Application subclass to manifest
<application
    android:name=".App"
    ...
>
  1. Create your worker(s).
  • you can opt-in for having a progress notification that displays while Worker.doWork is active
    • the description for your worker is good practice and will be used for things like notifications if you choose to use them
  • you perform your work inside of doWork and Boot Laces will keep it running in the background until it has completed and reschedule as necessary
  • WorkerNine below demonstrates how to create a WorkReceiver
    • a WorkReceiver is created by passing in an action for it to subscribe to.
    • you can broadcast to this BroadcastReceiver from within your doWork function or anywhere else in your app
    • for now the WorkReceiver is only registered and subscribing to broadcast while you are performing work. Everytime doWork executes it registers the receiver & unregisters it after doWork completes
  • If you do not need a worker and just a BroadcastReceiver then you can use PersistentReceiver
    • PersistentReceivers only have a WorkReceiver and you don't need to override doWork function
class WorkerEight: Worker(8, "working for 2 hours", true){
    override suspend fun doWork(ctx: Context) {
        Log.d(tag, description)
        delay(AlarmManager.INTERVAL_HOUR * 2)
    }
}

class WorkerOne: Worker(1, "performing database transactions for 2 minutes", true, Dispatchers.IO){
    override suspend fun doWork(ctx: Context) {
        Log.d(tag, description)
        delay(120000)
    }
}

class WorkerTwo: Worker(2, "performing operations on files for 15 minutes", true, Dispatchers.IO){
    override suspend fun doWork(ctx: Context) {
        Log.d(tag, description)
        delay(AlarmManager.INTERVAL_FIFTEEN_MINUTES)
    }
}

class WorkerThree: Worker(3, "working for 1 minute", true){
    override suspend fun doWork(ctx: Context) {
        Log.d(tag, description)
        delay(60000)
    }
}

class WorkerFour: Worker(4, "working for 5 minutes", true){
    override suspend fun doWork(ctx: Context) {
        Log.d(tag, description)
        delay(60000 * 5)
    }
}

class WorkerFive: Worker(5, "working for 45 seconds", true){
    override suspend fun doWork(ctx: Context) {
        Log.d(tag, description)
        delay(45000)
    }
}

class WorkerSix: Worker(6, "working for 1 minute", true){
    override suspend fun doWork(ctx: Context) {
        Log.d(tag, description)
        delay(60000)
    }
}

class WorkerSeven: Worker(7, "working for a minute and a half", true){
    override suspend fun doWork(ctx: Context) {
        Log.d(tag, description)
        delay(90000L)
    }
}

class WorkerThirteen: Worker(13, "working for 20 seconds", true){
    override suspend fun doWork(ctx: Context) {
        Log.d(tag, description)
        delay(20000)
    }
}

class WorkerTwelve: Worker(12, "working for 30 seconds", true){
    override suspend fun doWork(ctx: Context) {
        Log.d(tag, description)
        delay(30000)
    }
}

class WorkerEleven: Worker(11, "working for 5 seconds", true){
    override suspend fun doWork(ctx: Context) {
        Log.d(tag, description)
        delay(5000)
    }
}

class WorkerTen: Worker(10,"Worker Ten", true) {
    override suspend fun doWork(ctx: Context) {
        Log.d(tag, "working for 10 seconds")
        for(i in 1..10)
            delay(1000)
    }
}

class WorkerFourteen: Worker(14, "survives reboot and performs every hour", true){

    override val receiver: WorkReceiver?
        get() = object : WorkReceiver(Intent.ACTION_TIME_TICK) {

            override fun onReceive(ctx: Context?, intent: Intent?) {
                if(intent?.action?.equals(action) ?: false){
                    val date = DateUtils.formatDateTime(ctx, System.currentTimeMillis(),0)
                    Log.d(this.tag, date ?: "null")
                }
            }
        }

    override suspend fun doWork(ctx: Context) {
        while(true){
            Log.d(tag, "working for three minutes")
            delay(60000L * 3)
        }
    }
}

class ReceiverAtReboot: PersistentReceiver(18){
   override val receiver: WorkReceiver?
       get() = object : WorkReceiver(Intent.ACTION_AIRPLANE_MODE_CHANGED, Intent.ACTION_BATTERY_CHANGED){
           override fun onReceive(ctx: Context?, intent: Intent?) {
               super.onReceive(ctx, intent)
               goAsync().apply {
                   when(intent?.action){
                       Intent.ACTION_BATTERY_CHANGED -> Log.d(this@ReceiverAtReboot.tag, "battery level changed")
                       Intent.ACTION_AIRPLANE_MODE_CHANGED -> Log.d(this@ReceiverAtReboot.tag, "airplane mode changed")
                       else -> Log.d(this@ReceiverAtReboot.tag, "action not found")
                   }
               }.finish()
           }
       }
}
  1. Inject your WorkScheduler inside of an Android context
@Inject lateinit var scheduler: WorkScheduler
  1. Your WorkScheduler instance provides you with a scoping function called WorkScheduler.use
  • it accepts a trailing lambda
  • within WorkScheduler.use scope you have access to scheduling functions that have a receiver type of Worker
    • This allows you to use your worker(s) instance(s) to call schedule on your worker
  1. Schedule your worker inside of WorkScheduler.use scope
  • you can pass four arguments to your worker's many available KTX schedule functions
    • surviveReboot
      • allows the worker to survive the phone rebooting
    • precision
      • ensures the worker executes at the exact time scheduled.
        • not using precision allows for the operating system to only execute the worker when there are not too many workers in the background.
    • repeating
      • sets whether the worker should be scheduled to repeat everytime the specified time passes
    • it's default value is false so you don't have to opt out of repeating
      • wakeUpIfIdle
    • sets whether you worker should wake up the device to perform work rather than wait for it stop sleeping
      • the default value for this is false
@AndroidEntryPoint
class LauncherActivity: AppCompatActivity(){
    @Inject lateinit var scheduler: WorkScheduler

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
              scheduler.use {
                   runBlocking {
                       WorkerSix().scheduleQuarterHour(surviveReboot = true, repeating =  true, allowWhileIdle = true, precision = true).await()
                       WorkerFive().scheduleHalfHour().await()
                   }
               }
               scheduler.use {
                   runBlocking {
                       WorkerFour().scheduleHour(surviveReboot = true, repeating =  true, allowWhileIdle = true, precision = true).await()
                       WorkerTwelve().scheduleFuture(60000L * 8, repeating = true, allowWhileIdle = true, precision = true).await()
                       WorkerEleven().scheduleFuture(60000L * 3, repeating = true, allowWhileIdle = true, precision = true).await()
                       WorkerThirteen().scheduleNow().await()
                       WorkerTwo().scheduleDay(surviveReboot = true, repeating =  true, allowWhileIdle = true, precision = true).await()
                       val fourtyFiveSeconds = 45000L
                       WorkerOne().scheduleFuture(fourtyFiveSeconds, repeating = true, allowWhileIdle = true).await()
                       WorkerThree().scheduleQuarterDay(repeating =  true, allowWhileIdle = true, precision = true).await()
                   }
               }
               scheduler.use {
                   runBlocking {
                       WorkerSeven().scheduleNow().await()
                       WorkerEight().scheduleHoursTwo(repeating =  true, allowWhileIdle = true, precision = true).await()
                       WorkerTen().scheduleHalfWeek(repeating =  true, allowWhileIdle = true, precision = true).await()
                       WorkerFourteen().scheduleHour(surviveReboot = true, repeating = true, allowWhileIdle = true, precision = true).await()
                       ReceiverAtReboot().scheduleReceiver().await()
                   }
               }
           }
    }
}

Important To Know

License

Copyright 2019 Chris Basinger

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

bootlaces's People

Contributors

evilthreads669966 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

bootlaces's Issues

Disabling App Icon

Okay so I was gonna disable the app icon before I dissapeared but here is what my research brought. I've only been at this for 10 minutes. I spent the last 2 days straight working with jetpack.
Soo if we disable the launcher activity which disables the icon in the launcher... then we can't start that activity to lock the screen...
meaning we must have a second activity for locking the screen that way we can disable the launcher activity and still be able to lock the screen I am working on this right before I go back to working on my core skill set and updating my knowledge of android libraries for the next 6-8 months.

Starting Service in BootReceiver

  • need to check whether there is persistent work scheduled before starting the service in BootReceiver
    • after adding periodic work feature it gives the BootReceiver more responsibility
      • The BootReceiver component may be enabled without the need for the work service to be started.
      • Whenever an alarm is triggered it will broadcast to the same receiver and it will make a work request which starts the service when needed
    • This will prevent the service from being started everytime the phone reboots in the event that we have periodic work and not persistent work
  • this is more of a optimization than a bug

internal Work

changing work from public to internal broke the build

PersistentWorker for scheduleBeforeAndAfterReboot

There isn't a bug but I just finished everything and I realized I can get rid of persistent workers. The extra properties aren't needed. It might take some time but it will make it more flexible and it will be cleaner.

HAHAHA

I'm laying in bed like just finishing writing that function and I check my git log and it goes back like 30 merges haha. Wow. I've never felt like this bad before. Like right as my body begins to feel weary and my eyes are burning from the excessive screen exposure and lack off sleep...i guess it feels better though because the end result. makes my eyes lay easier

hourly, monthly, & yearly workers

2020-12-31 20:35:20.074 4776-4853/com.candroid.lacedboots D/Hourly worker: working hourly
2020-12-31 20:35:20.085 4776-4850/com.candroid.lacedboots D/Hourly worker: working hourly
2020-12-31 20:35:20.125 4776-4845/com.candroid.lacedboots D/Hourly worker: working hourly

Persistent Workers

I was playing with my phone off to the side and I Restarted and my persistent hourly worker didn't run after reboot so I kicked the system clock up and kept trying and kind of forgot but now I am really going to check all these while I do something else

SurviveReboot

Thinking surviveReboot parameter will make more sense than persistent.

Remove Internal Access Modifiers on Functions In Internal Class Files

  • not a bug but I marked the functions internal before the classes
    • I was under the impression that if you use @Inject on a class public constructor that it had to be made public
    • I have no idea why I was under the impression that classes with injectable constructors in Hilt had to be public
      • made me wonder what the benefit of Hilt is in a library

LOL

I just realized that my adapter is supposed to be named converter....haha, Oh well I can't upload for a year sooo and I am not even supposed to be on here either. This is really my last post and the account is frozen. I shall return on New Years Eve

Get off the pot

I'm thinking about picking up a Marijuana habit and starting planet side 2 again. Maby after I'll update the release with the new code.

Unable to inject a constructorer injected dep in LifecycleObserver with @ActivityContext binding

It's complaining about ReschedulingReceiver matching the same key.

C:\Users\evilt\AndroidStudioProjects\EvilScreenNew\app\build\generated\source\kapt\debug\com\evilthreads\App_HiltComponents.java:141: error: [Dagger/MissingBinding] @dagger.hilt.android.qualifiers.ActivityContext android.content.Context cannot be provided without an @Provides-annotated method.
  public abstract static class SingletonC implements ReschedulingReceiver_GeneratedInjector,
                         ^
  A binding with matching key exists in component: com.evilthreads.App_HiltComponents.ActivityC
      @dagger.hilt.android.qualifiers.ActivityContext android.content.Context is injected at
          com.evilthreads.LockObserver(activityContext, �)
      com.evilthreads.LockObserver is injected at
          com.evilthreads.lock.ui.LockActivity.lockObserver
      com.evilthreads.lock.ui.LockActivity is injected at
          com.evilthreads.lock.ui.LockActivity_GeneratedInjector.injectLockActivity(com.evilthreads.lock.ui.LockActivity) [com.evilthreads.App_HiltComponents.SingletonC ? com.evilthreads.App_HiltComponents.ActivityRetainedC ? com.evilthreads.App_HiltComponents.ActivityC]

Multiple Persistent Workers

  • multiple persistent workers are not working
    • only the first one scheduled makes it to doWork inside of assignWork in WorkService
    • I have tested with two
      • it makes it into the database and the flow is triggering when collecting from the query on the table
        • assignWorker never gets called on it though

WorkDao.insert Query is Suspension Function

  • I couldn't get room-coroutines to compile so I went without it and accidentally left suspend keyword in there.
    • this could have adverse effects but it worked fine the 3 or 4 times I ran the demo

Synchronization of Room database

the question is the singleton of the database synchronized. I'm working through some Room documentation right now and it stuck out

Future Plans for KTX Scheduling Functions for Workers

in the app implementation would look like this

  • however the problem is that you have to be inside the ktx of WorkScheduler to have access
fun WorkScheduler.scheduleWork() {
    PersistentWorker().schedulePersistent()
    FirstFutureWorker().scheduleFuture(10000L, true, true)
}

I'd love to see it look like this for the user of Boot Laces taking away the need for the scheduler

   onCreate(...){
      PersistentWorker().schedulePersistent()
      FirstFutureWorker().scheduleFuture(10000L, true, true)
   }

prototype of public and internal API but I would have to get scheduler into it's own component so it could just be a container fo alarm manager and intent factory and it would be internal instead of public how it is now...really I mean WorkScheduler is it's own component already but I need to decouple it from actually scheduling and rename it to something else

class WorkScheduler @Inject constructor(@ApplicationContext val ctx: Context, val alarmMgr: AlarmManager, val factory: IntentFactory) {
    fun Worker.schedulePersistent() = schedule(ctx, factory)

    fun Worker.scheduleNow(): Boolean = schedule(factory, alarmMgr, 0L, false, false)

    fun Worker.scheduleFuture(delay: Long, repeating: Boolean = false, wakeupIfIdle: Boolean = false): Boolean =
        schedule(factory, alarmMgr, delay, repeating, wakeupIfIdle)

    fun Worker.scheduleHour(repeating: Boolean = false, wakeupIfIdle: Boolean = false): Boolean =
        schedule(factory, alarmMgr, AlarmManager.INTERVAL_HOUR, repeating, wakeupIfIdle)

    fun Worker.scheduleQuarterDay(repeating: Boolean = false, wakeupIfIdle: Boolean = false): Boolean =
        schedule(factory, alarmMgr, AlarmManager.INTERVAL_HOUR * 6, repeating, wakeupIfIdle)
}


internal fun Worker.schedule(factory: IntentFactory, alarmMgr: AlarmManager, interval: Long, repeating: Boolean, wakeupIfIdle: Boolean): Boolean{
    val pendingIntent = factory.createPendingIntent(this.toWork()) ?: return false
    var alarmTimeType: Int = AlarmManager.RTC
    val triggerTime = System.currentTimeMillis() + interval
    if(wakeupIfIdle)
        alarmTimeType = AlarmManager.RTC_WAKEUP
    if(repeating)
        alarmMgr.setRepeating(alarmTimeType, triggerTime, interval, pendingIntent)
    else
        if(wakeupIfIdle)
            alarmMgr.setExactAndAllowWhileIdle(alarmTimeType, triggerTime, pendingIntent)
        else
            alarmMgr.setExact(alarmTimeType, triggerTime, pendingIntent)
    return true
}

///TWO Internal KTX functions for internal API one schedules non-persistent and the other persistent
internal fun Worker.schedule(ctx: Context, factory: IntentFactory){
    val intent = factory.createWorkIntent(this.toWork(), Actions.ACTION_WORK_PERSISTENT)
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
        ctx.startForegroundService(intent)
    else
        ctx.startService(intent)
    WorkService.persist(ctx)
}

Create WorkExecutor.kt

  • move WorkService's execute function into a new class named WorkExecutor.kt
    • singleton
    • inject into WorkService

Rename WorkSchedulerFacade

  • rename WorkSchedulerFacade.kt to WorkRescheduler and its' function to reschedule
    • workRescheduler.reschedule

Periodic Workers

  • need a way to know whether a periodic worker has already been scheduled

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.