Skip to content

Commit

Permalink
Add support to Service (#31)
Browse files Browse the repository at this point in the history
* Add support to Service

* cr fixes
  • Loading branch information
denys-vasylenko authored May 2, 2022
1 parent 98c396c commit 7fbb9e0
Show file tree
Hide file tree
Showing 8 changed files with 169 additions and 0 deletions.
3 changes: 3 additions & 0 deletions sample/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.deliveryhero.whetstone">

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

<application
android:name=".MainApplication"
android:icon="@mipmap/ic_launcher"
Expand All @@ -18,6 +20,7 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name=".MainService" />
</application>

</manifest>
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.deliveryhero.whetstone

import android.content.Intent
import android.os.Bundle
import android.widget.Toast
import androidx.activity.viewModels
Expand All @@ -19,10 +20,18 @@ public class MainActivity : AppCompatActivity() {
viewModelFactoryProducer.createViewModelFactory(this)
}

private val serviceIntent by lazy { Intent(this, MainService::class.java) }

override fun onCreate(savedInstanceState: Bundle?) {
Whetstone.inject(this)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Toast.makeText(this, viewModel.getHelloWorld(), Toast.LENGTH_SHORT).show()
startService(serviceIntent)
}

override fun onDestroy() {
stopService(serviceIntent)
super.onDestroy()
}
}
17 changes: 17 additions & 0 deletions sample/src/main/java/com/deliveryhero/whetstone/MainApplication.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
package com.deliveryhero.whetstone

import android.app.Application
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.NotificationManager.IMPORTANCE_DEFAULT
import android.content.Context
import android.os.Build
import android.util.Log
import com.deliveryhero.whetstone.MainService.Companion.NOTIFICATION_CHANNEL_ID
import com.deliveryhero.whetstone.component.ApplicationComponent
import com.deliveryhero.whetstone.component.ApplicationComponentOwner
import com.deliveryhero.whetstone.injector.ContributesInjector
Expand All @@ -21,5 +27,16 @@ public class MainApplication : Application(), ApplicationComponentOwner {
Whetstone.inject(this)
super.onCreate()
Log.d("App", dependency.getMessage("Application"))
registerNotificationChannel()
}

private fun registerNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
(getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager).apply {
createNotificationChannel(
NotificationChannel(NOTIFICATION_CHANNEL_ID, "Main Channel", IMPORTANCE_DEFAULT)
)
}
}
}
}
40 changes: 40 additions & 0 deletions sample/src/main/java/com/deliveryhero/whetstone/MainService.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.deliveryhero.whetstone

import android.app.Service
import android.content.Intent
import android.os.IBinder
import androidx.core.app.NotificationCompat
import com.deliveryhero.whetstone.injector.ContributesInjector
import com.deliveryhero.whetstone.scope.ServiceScope
import javax.inject.Inject

@ContributesInjector(ServiceScope::class)
class MainService : Service() {

@Inject
lateinit var dependency: MainDependency

override fun onCreate() {
Whetstone.inject(this)
super.onCreate()
}

override fun onBind(intent: Intent?): IBinder? = null

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {

val notification = NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
.setContentTitle("${this.javaClass.simpleName} is running")
.setContentText(dependency.getMessage("Service"))
.setSmallIcon(R.drawable.ic_baseline_textsms_24)
.setAutoCancel(true)
.build()
startForeground(1, notification)

return START_NOT_STICKY
}

companion object {
const val NOTIFICATION_CHANNEL_ID = "MainChannel"
}
}
10 changes: 10 additions & 0 deletions sample/src/main/res/drawable/ic_baseline_textsms_24.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M20,2L4,2c-1.1,0 -1.99,0.9 -1.99,2L2,22l4,-4h14c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM9,11L7,11L7,9h2v2zM13,11h-2L11,9h2v2zM17,11h-2L15,9h2v2z"/>
</vector>
42 changes: 42 additions & 0 deletions whetstone/src/main/java/com/deliveryhero/whetstone/Whetstone.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ package com.deliveryhero.whetstone
import android.annotation.SuppressLint
import android.app.Activity
import android.app.Application
import android.app.Service
import android.content.ContextWrapper
import android.view.View
import androidx.annotation.IdRes
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.FragmentFactory
import androidx.lifecycle.Lifecycle
import com.deliveryhero.whetstone.component.ActivityComponent
import com.deliveryhero.whetstone.component.ServiceComponent
import com.deliveryhero.whetstone.component.ApplicationComponent
import com.deliveryhero.whetstone.component.ApplicationComponentOwner
import com.deliveryhero.whetstone.component.ViewComponent
Expand All @@ -31,6 +33,7 @@ public object Whetstone {
root.updateAndGet { component -> component ?: initializer() }
}

@Suppress("MemberVisibilityCanBePrivate")
public fun <T : Any> fromApplication(application: Application): T {
require(application is ApplicationComponentOwner) {
"Application must implement ${ApplicationComponentOwner::class.java.name} to use this Injector"
Expand All @@ -41,6 +44,7 @@ public object Whetstone {
/**
* Returns the component interface from an [activity].
*/
@Suppress("MemberVisibilityCanBePrivate")
public fun <T : Any> fromActivity(activity: Activity): T {
val contentView = activity.findViewById<View>(android.R.id.content)
return contentView.getTagOrSet(R.id.activityComponentId) {
Expand All @@ -50,6 +54,16 @@ public object Whetstone {
} as T
}

/**
* Returns the component interface from a [service].
*/

private fun <T : Any> fromService(service: Service): T {
return fromApplication<ServiceComponent.ParentComponent>(service.application)
.getServiceComponentFactory()
.create(service) as T
}

/**
* A helper that let you inject dependencies into the fields and methods of an [Application].
*
Expand Down Expand Up @@ -97,6 +111,34 @@ public object Whetstone {
injector?.injectMembers(activity)
}

/**
* A helper that let you inject default dependencies into the fields and methods of a [Service].
*
* For example:
* ```
* @ContributesInjector(ServiceScope::class)
* class CustomService: Service() {
*
* @Inject lateinit var someDep: SomeDep
*
* override fun onCreate() {
* Whetstone.inject(this)
* super.onCreate()
* }
* }
* ```
*
* Services that use this method must have the [ContributesInjector] annotation,
* and they must have at least 1 `@Inject` field or method. Otherwise, calling this method
* will result in an [IllegalStateException]
*/
public fun inject(service: Service) {
val injector = fromService<ServiceComponent>(service)
.getMembersInjectorMap()[service.javaClass] as? MembersInjector<Service>

requireNotNull(injector).injectMembers(service)
}

public fun inject(view: View) {
val activity = view.findActivity()
val injector = fromActivity<ViewComponent.ParentComponent>(activity)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.deliveryhero.whetstone.component

import android.app.Service
import com.deliveryhero.whetstone.SingleIn
import com.deliveryhero.whetstone.injector.MembersInjectorMap
import com.deliveryhero.whetstone.scope.ApplicationScope
import com.deliveryhero.whetstone.scope.ServiceScope
import com.squareup.anvil.annotations.ContributesSubcomponent
import com.squareup.anvil.annotations.ContributesTo
import dagger.BindsInstance
import dagger.Module
import dagger.multibindings.Multibinds

/**
* A Dagger component that has the lifetime of the [android.app.Service].
*/
@ContributesSubcomponent(scope = ServiceScope::class, parentScope = ApplicationScope::class)
@SingleIn(ServiceScope::class)
public interface ServiceComponent {
public fun getMembersInjectorMap(): MembersInjectorMap

/**
* Interface for creating a [ServiceComponent].
*/
@ContributesSubcomponent.Factory
public interface Factory {
public fun create(@BindsInstance service: Service): ServiceComponent
}

@ContributesTo(ApplicationScope::class)
public interface ParentComponent {
public fun getServiceComponentFactory(): Factory
}
}

@Module
@ContributesTo(ServiceScope::class)
public interface ServiceModule {

@Multibinds
public fun membersInjectors(): MembersInjectorMap
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.deliveryhero.whetstone.scope

/**
* Scope marker class for bindings that should exist for the life of a [android.app.Service].
*/
public class ServiceScope private constructor()

0 comments on commit 7fbb9e0

Please sign in to comment.