From 7fbb9e03906b5d1e214a75e1ddad7298c4e3cb0f Mon Sep 17 00:00:00 2001 From: Denys Vasylenko Date: Mon, 2 May 2022 13:57:12 +0200 Subject: [PATCH] Add support to Service (#31) * Add support to Service * cr fixes --- sample/src/main/AndroidManifest.xml | 3 ++ .../deliveryhero/whetstone/MainActivity.kt | 9 ++++ .../deliveryhero/whetstone/MainApplication.kt | 17 ++++++++ .../com/deliveryhero/whetstone/MainService.kt | 40 ++++++++++++++++++ .../res/drawable/ic_baseline_textsms_24.xml | 10 +++++ .../com/deliveryhero/whetstone/Whetstone.kt | 42 +++++++++++++++++++ .../whetstone/component/ServiceComponent.kt | 42 +++++++++++++++++++ .../whetstone/scope/ServiceScope.kt | 6 +++ 8 files changed, 169 insertions(+) create mode 100644 sample/src/main/java/com/deliveryhero/whetstone/MainService.kt create mode 100644 sample/src/main/res/drawable/ic_baseline_textsms_24.xml create mode 100644 whetstone/src/main/java/com/deliveryhero/whetstone/component/ServiceComponent.kt create mode 100644 whetstone/src/main/java/com/deliveryhero/whetstone/scope/ServiceScope.kt diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml index 9fe93ce..a08125c 100644 --- a/sample/src/main/AndroidManifest.xml +++ b/sample/src/main/AndroidManifest.xml @@ -2,6 +2,8 @@ + + + \ No newline at end of file diff --git a/sample/src/main/java/com/deliveryhero/whetstone/MainActivity.kt b/sample/src/main/java/com/deliveryhero/whetstone/MainActivity.kt index a0c4a49..fc73a04 100644 --- a/sample/src/main/java/com/deliveryhero/whetstone/MainActivity.kt +++ b/sample/src/main/java/com/deliveryhero/whetstone/MainActivity.kt @@ -1,5 +1,6 @@ package com.deliveryhero.whetstone +import android.content.Intent import android.os.Bundle import android.widget.Toast import androidx.activity.viewModels @@ -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() } } diff --git a/sample/src/main/java/com/deliveryhero/whetstone/MainApplication.kt b/sample/src/main/java/com/deliveryhero/whetstone/MainApplication.kt index 45b724a..7777f46 100644 --- a/sample/src/main/java/com/deliveryhero/whetstone/MainApplication.kt +++ b/sample/src/main/java/com/deliveryhero/whetstone/MainApplication.kt @@ -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 @@ -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) + ) + } + } } } diff --git a/sample/src/main/java/com/deliveryhero/whetstone/MainService.kt b/sample/src/main/java/com/deliveryhero/whetstone/MainService.kt new file mode 100644 index 0000000..1ae109c --- /dev/null +++ b/sample/src/main/java/com/deliveryhero/whetstone/MainService.kt @@ -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" + } +} diff --git a/sample/src/main/res/drawable/ic_baseline_textsms_24.xml b/sample/src/main/res/drawable/ic_baseline_textsms_24.xml new file mode 100644 index 0000000..ab72164 --- /dev/null +++ b/sample/src/main/res/drawable/ic_baseline_textsms_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/whetstone/src/main/java/com/deliveryhero/whetstone/Whetstone.kt b/whetstone/src/main/java/com/deliveryhero/whetstone/Whetstone.kt index b8a937b..de09979 100644 --- a/whetstone/src/main/java/com/deliveryhero/whetstone/Whetstone.kt +++ b/whetstone/src/main/java/com/deliveryhero/whetstone/Whetstone.kt @@ -3,6 +3,7 @@ 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 @@ -10,6 +11,7 @@ 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 @@ -31,6 +33,7 @@ public object Whetstone { root.updateAndGet { component -> component ?: initializer() } } + @Suppress("MemberVisibilityCanBePrivate") public fun fromApplication(application: Application): T { require(application is ApplicationComponentOwner) { "Application must implement ${ApplicationComponentOwner::class.java.name} to use this Injector" @@ -41,6 +44,7 @@ public object Whetstone { /** * Returns the component interface from an [activity]. */ + @Suppress("MemberVisibilityCanBePrivate") public fun fromActivity(activity: Activity): T { val contentView = activity.findViewById(android.R.id.content) return contentView.getTagOrSet(R.id.activityComponentId) { @@ -50,6 +54,16 @@ public object Whetstone { } as T } + /** + * Returns the component interface from a [service]. + */ + + private fun fromService(service: Service): T { + return fromApplication(service.application) + .getServiceComponentFactory() + .create(service) as T + } + /** * A helper that let you inject dependencies into the fields and methods of an [Application]. * @@ -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(service) + .getMembersInjectorMap()[service.javaClass] as? MembersInjector + + requireNotNull(injector).injectMembers(service) + } + public fun inject(view: View) { val activity = view.findActivity() val injector = fromActivity(activity) diff --git a/whetstone/src/main/java/com/deliveryhero/whetstone/component/ServiceComponent.kt b/whetstone/src/main/java/com/deliveryhero/whetstone/component/ServiceComponent.kt new file mode 100644 index 0000000..4d4c81f --- /dev/null +++ b/whetstone/src/main/java/com/deliveryhero/whetstone/component/ServiceComponent.kt @@ -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 +} diff --git a/whetstone/src/main/java/com/deliveryhero/whetstone/scope/ServiceScope.kt b/whetstone/src/main/java/com/deliveryhero/whetstone/scope/ServiceScope.kt new file mode 100644 index 0000000..98cad51 --- /dev/null +++ b/whetstone/src/main/java/com/deliveryhero/whetstone/scope/ServiceScope.kt @@ -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()