Injecting Context into a Broadcast Receiver using Hilt/Dagger? - kotlin

I am using context() in my GlanceAppWidget() for tasks like retrieving glanceId and updating app widget state. I am having issue with how I inject the context object.
I would like to use the dagger/hilt framework to inject the context into my GlanceAppWidget() constructor. See MyWidget() below.
However by injecting the context into MyWidget, I then need to pass the context as constructor parameter in MyWidgetReceiver() for val glanceAppWidget. Broadcast receivers are not meant to have constructor arguments so this gives me an Instantiation Exception.
How can I inject context into my GlanceAppWidget? Any help will be much appreciated.
Note: I have also tried using default arguments in MyWidget() to avoid providing context in MyWidgetReceiver but this throws "Type may only contain one injected constructor".
#Singleton
class MyWidget #Inject constructor(
#ApplicationContext val context: Context
) : GlanceAppWidget()
#AndroidEntryPoint
#Singleton
class MyWidgetReceiver #Inject constructor(
#ApplicationContext val context: Context /*<-java.lang.InstantiationException when trying to inject into BroadcastReceiver*/
) : GlanceAppWidgetReceiver() {
override val glanceAppWidget: GlanceAppWidget
get() = MyWidget(context)
}

onReceive method of the BroadcastReceiver has the context as its argument. You probably want to bind your widget-creating logic to this method
fun onReceive(context: Context, intent: Intent)
EDIT:
I did not noticed that you are using Glance. Since that, I recommend to stop using context in constructor and instead update glanceId and widget state when you will actually have access to context via some kind of method.
override fun onReceive(context: Context, intent: Intent) {
glanceAppWidget.update(context)
}
MyWidget:
fun update(context: Context) {
// do some work
}
In case when you will need update, you will simply send matching intent which will be received by the receiver.

Couple of things:
GlanceAppWidgetReceiver is a BroadcastReceiver thus you can't have constructor parameters. Also it shouldn't be a singleton. BroadcastReceivers are short-term living classes.
You can retrieve the context inside a #Composable function by calling LocalContext.current. Also you can retrieve the glanceId by calling LocalGlanceId.current
Thus you don't need to inject the context in the first place.
class MyWidget: GlanceAppWidget() {
#Composable
override fun Content() {
val context = LocalContext.current
val glanceId = LocalGlanceId.current
//...
}
}
#AndroidEntryPoint
class MyWidgetReceiver: GlanceAppWidgetReceiver() {
override val glanceAppWidget = MyWidget()
}

Related

Possible to use Spring AOP (AspectJ) with Kotlin properties?

Is it possible to use Spring AOP (AspectJ) with Kotlin properties? Specifically due to how Kotlin compiles properties to Java:
a getter method, with the name calculated by prepending the get prefix
a setter method, with the name calculated by prepending the set prefix (only for var properties)
a private field, with the same name as the property name (only for properties with backing fields)
Consider the following minimal reproducible example:
#Retention(AnnotationRetention.RUNTIME)
#Target(AnnotationTarget.PROPERTY, AnnotationTarget.FUNCTION)
annotation class TestAnnotation
...
#Aspect
class TestAspect {
#Around("#annotation(annotation)")
fun throwingAround(joinPoint: ProceedingJoinPoint, annotation: TestAnnotation): Any? {
throw RuntimeException()
}
}
...
internal class MinimalReproducibleExample {
open class TestProperties {
#TestAnnotation
val sampleProperty: String = "sample property"
#TestAnnotation
fun sampleFunction(): String = "sample function"
}
private lateinit var testProperties: TestProperties
#BeforeEach
fun setUp() {
val aspectJProxyFactory = AspectJProxyFactory(TestProperties())
aspectJProxyFactory.addAspect(TestAspect())
val aopProxyFactory = DefaultAopProxyFactory()
val aopProxy = aopProxyFactory.createAopProxy(aspectJProxyFactory)
testProperties = aopProxy.proxy as TestProperties
}
#Test
fun test() {
println(testProperties.sampleProperty)
println(testProperties.sampleFunction())
}
}
Running the test yields:
null
sample function
When debugging I can see that the generated proxy is a cglib-backed proxy, which should be able to proxy to a concrete class, but it does not seem to invoke the configured aspect. Is there something wrong with my #Around definition, or is this a limitation of Kotlin properties and/or proxying concrete classes?
Was able to trigger the aspect above with the following changes:
Use a "site target" for the getter: #get:TestAnnotation
Make the property/function both open

DDD, companion object and UninitializedPropertyAccessException lateinit property bus has not been initialized

i'm quite new to Kotlin and having troubles understanding my issue with initalizing a vertx EventBus.
I'm using Kotlin with Quarkus (which might not be to relevant here) and Vertx in a DDD context.
I have a model, which provides a static method for sending an update. This static method creates the model and the model tries to inject the EventBus:
package model
import java.util.*
import javax.enterprise.context.ApplicationScoped
#ApplicationScoped
class EventSet(
) {
var events = listOf<Event>()
#field:Default
#Inject
lateinit var bus: EventBus
// kotlin.UninitializedPropertyAccessException: lateinit property bus has not been initialized
fun sendAndStore() {
... persist() happens here and other things
bus.publish(UPDATE, entity)
}
companion object {
const val UPDATE = "update"
fun create(events: List<Event>): EventSet {
if (events.isEmpty()) {
throw Exception()
}
val eventSet = EventSet()
eventSet.events = events
return eventSet
}
}
}
When i'm running my unit test (Annotated with #QuarkusTest on the class, #Test and #TestTransaction on the function, i'm getting kotlin.UninitializedPropertyAccessException: lateinit property bus has not been initialized
I don't understand it at all. My assumption was that i can inject it whenever i want. Unfortunate the constructor injection would require me to loop through it through the companion object as i was also not able to inject anything into my static method (which i assume shouldn't work anyway).
How do i debug this type of issue? I checked the quarkus dev console and the Vertx/EventBus is marked as a #Singleton bean, not sure if that makes any difference.
Thanks,
Sigi

Problem with creating secondary constructor kotlin

Can someone explain me why I cannot create empty secondary construcor in my class?
I wanna TEST it but I need to create a instance of class to use the methods from, but my class need a parametr to create it. I thought to create a scecondary constructor but when I'm trying it makes a error "There's a cycle in the delegation calls chain". Excatly I wanna use it on this #TEST below but when I'm trying to create instance of Adapter class I must put there also (FragmentManager) inside. Any ideas?
class Adapter(sFM: FragmentManager) : FragmentPagerAdapter(sFM, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
constructor() : this()
private val pFragmentList = ArrayList<Fragment>()
private val pFragmentTitle = ArrayList<String>()
override fun getCount(): Int = pFragmentList.size
override fun getItem(position: Int): Fragment = pFragmentList[position]
override fun getPageTitle(position: Int): CharSequence = pFragmentTitle[position]
fun addFragment(fm: Fragment, title: String) {
pFragmentList.add(fm)
pFragmentTitle.add(title)
}
}
#Test
fun `create instance of class Adapter`() {
var adapter = Adapter().addFragment()
}
There is no FragmentPagerAdapter with empty constructor. Basically, what your code is trying to compile, is to do constructor that calls itself. If you want to use base class constructor you need to use super instead of this. But still, you won't find such constructor in base class. You always have to pass some FragmentManager

Kotlin Dagger 2 Sharing ViewModel between Activity and Fragment

As mentioned in the title I've got a problem with Dagger 2 injection. I have a single activity and multiple fragments. I'm trying to share activity's ViewModel with every child fragment. I based my solution on Google's Android Architecture ToDo sample. I've created ViewModelFactory as
ViewModelFactory
If you jump to the link you will see that in their solution there's a separate ViewModel for every activity and fragment. They are not showing how to deal with scenarios like mine. My implementation of ActivityModule looks like:
#Module
abstract class SampleModule {
#ContributesAndroidInjector(
modules = [
ViewModelBuilder::class
]
)
internal abstract fun sampleActivity(): SampleActivity
#Binds
#IntoMap
#ViewModelKey(SampleViewModel::class)
abstract fun bindViewModel(viewModel: SampleViewModel): ViewModel
}
My activity extends DaggerAppCompatActivity and fragment DaggerFragment and as follows my injection of view model looks simple as
class SampleActivity : DaggerAppCompatActivity() {
#Inject
lateinit var viewModel: SampleViewModel
...
I can't find a web solution to my problem. I'm a pretty novice user of Dagger. I've tried to implement Subcomponent but still, it's not working because all the examples I have searched so far didn't use DaggerApplication, DaggerAppCompatActivity and my way of injection. Please suggest any solution or if a subcomponent way is right please show me how to do it if it's possible in my current architecture.
Thank you very much in advance.
#silaros88 I was facing yor same issue, share a ViewModel between multiple fragmnets in a single Activity application, and i solved playing with the ViewmModelStoreOwner.
Steps to fix your issue.
See here TasksFragment.kt how they inject the ViewModelProvider.Factory instead of the ViewModel
Retrieve the desire ViewModel using one of this two options:
viewModels<SharedDesireViewModel> (requireActivity()) { Injected ViewModelProvider.Factory }
ViewModelProvider(requireActivity(), Injected ViewModelProvider.Factory ).get(SharedDesireViewModel::class.java)
Examples:
Option #1:
FragmentA.kt
class FragmentA: DaggerFragment() {
#Inject
lateinit var viewModelProviderFactory: ViewModelProvider.Factory
private val mainViewModel: MainViewModel by viewModels({requireActivity()}) { viewModelProviderFactory }
......
FragmentB.kt
class FragmentB: DaggerFragment() {
#Inject
lateinit var viewModelProviderFactory: ViewModelProvider.Factory
private val mainViewModel: MainViewModel by viewModels({requireActivity()}) { viewModelProviderFactory }
......
Option #2:
FragmentA.kt
class FragmentA: DaggerFragment() {
#Inject
lateinit var viewModelProviderFactory: ViewModelProvider.Factory
private val mainViewModel: MainViewModel by lazy {
ViewModelProvider(requireActivity(), viewModelProviderFactory)
.get(MainViewModel::class.java)
}
......
FragmentB.kt
class FragmentB: DaggerFragment() {
#Inject
lateinit var viewModelProviderFactory: ViewModelProvider.Factory
private val mainViewModel: MainViewModel by lazy {
ViewModelProvider(requireActivity(), viewModelProviderFactory)
.get(MainViewModel::class.java)
}
......
I solved the problem with a slightly different approach.
Since the fragments and the activity both have dagger modules.
In the ActivityModule I am providing the sharedViemodel as below
#Module
class ActivityModule(private val activity: AppCompatActivity)
{
#Provides
fun provideMainSharedVieModel() : MainSharedViewModel =
ViewModelProvider(activity).get(MainSharedViewModel::class.java)
}
And in my fragment module I am again providing the same viemodel as below:
#Module
class FragmentModule(private val fragment: Fragment)
{
#Provides
fun provideMainSharedVieModel() : MainSharedViewModel =
ViewModelProvider(fragment.activity!!).get(MainSharedViewModel::class.java)
}
Since the ViewModels are stored in a map with the activity or fragments as the key, hence providing the sharedViewModel with "fragment.activity!!" in the Fragment module will not create a new instance of the viewmodel , it will just the return the already instantiated shared viewmodel to the fragment.

Koin injecting into WorkManager

I have a basic work manager
class BackgroundSyncWorker (
appContext: Context,
workerParams: WorkerParameters
): Worker(appContext, workerParams) {
override fun doWork(): Result {
return Result.success()
}
}
And I want to inject my repository into this to do some work in my database. I've set Koin up correctly but can't seem to find a way of how to inject my dependency into the Worker. I've tried inheriting the KoinComponent and trying to do it using that, but by inject() doesn't exist, but there's two by inject methods that I can't find how to use. There doesn't seem to be any information on how to inject into managers, although there's a few for using dagger.
This does actually work, I was just using var instead of val.
class BackgroundSyncWorker (
appContext: Context,
workerParams: WorkerParameters
): Worker(appContext, workerParams), KoinComponent {
val dataSyncRepository : DataSyncRepositoryImpl by inject()
override fun doWork(): Result {
return Result.success()
}
}
I have noticed a couple of things from your code:
The first reason for why this does not work because you need to extend/inherit the BackgroundSyncWork from KoinComponent, so making this BackgroundSyncWork koin-aware.
class BackgroundSyncWorker (
appContext: Context,
workerParams: WorkerParameters
): Worker(appContext, workerParams), KoinComponent {
val database: Database by inject()
override fun doWork(): Result {
return Result.success()
}
}
Second: Also, please make sure that database object creation is properly configured in koin module. It should work with no problem.