Can't run unit tests with RobolectricTestRunner and Koin - android

I have a test class with RobolectricTestRunner which I use for getting application context and also I extend one class with KoinComponent. When I started my test it returned java.lang.IllegalStateException: KoinApplication has not been started and points to my class that extends KoinComponent. I tried to start Koin in setUp() method with loading modules and removed Robolectric but in this way it can't find application context. Is there a way to write unit test with Robolectric and Koin?

As you can read here, BroadcastReceivers declared in the AndroidManifest get created before your Application's onCreate. Therefore, Koin is not yet initialized. A workaround for that is to create a Helper for your Broadcast Receiver and initialize the Helper lazy:
class MyBroadcastReceiver : BroadcastReceiver() {
// Broadcast Receivers declared in the AndroidManifest get created before your Application's onCreate.
// The lazy initialization ensures that Koin is set up before the broadcast receiver is used
private val koinHelper: BroadcastReceiverHelper
by lazy { BroadcastReceiverHelper() }
override fun onReceive(context: Context, intent: Intent) {
koinHelper.onReceive(context, intent)
}
}
class BroadcastReceiverHelper : KoinComponent {
private val myClassToInject: MyClassToInject by inject()
fun onReceive(context: Context, intent: Intent) {
// do stuff here
}
}

Related

How to make a Repository singleton for a Foreground Service and Fragments?

I have a Repository(context: Context) class (accepts a context) that must be singleton.
In normal cases it is easy to do. But in my app, I have a Foreground Service that will be running even when the app is removed from the recent apps.
And I have to use the Repository object inside this Foreground Service and as well as inside other Fragments in the app.
What is the best way to make the Repository singleton?
Currently I am using dagger-hilt to inject the Repository inside the Service class. And I am not sure if it is the right way to do it.
Here are the code samples:
MainApplication.kt
#HiltAndroidApp
class MainApplication: Application() {}
HiltModule.kt
#Module
#InstallIn(SingletonComponent::class)
object HiltModule {
#Singleton
#Provides
fun getDataStore(#ApplicationContext mContext: Context) = Repository(mContext)
}
ForegroundService.kt
#AndroidEntryPoint
class ForegroundService : Service() {
#Inject
lateinit var dataRepo: Repository
}
I don't know anything about Hilt, but you can use a bound service if you're okay with foregoing constructor injection. You could setup a binding between your activity and the foreground service, and then pass in your shared Repository instance once the service is live and has registered itself. As I understand it, this is the recommended way to communicate between an activity and service.
I think this will also help with the lifecycle issue you bring up in the comments. I'm using this approach on a foreground service that has a different lifecycle than my MainActivity, the activity gets destroyed and the service keeps running in the background, no problems. It also starts back up and syncs with the foreground process without issue. Hilt may be inadvertently causing lifecycle issues.
Edit: Just came across this comment
Common Android DI solutions do not work across processes. If you
elected to have 2+ processes (e.g., UI process and foreground service
process), you cannot inject the same object into both processes using,
say, Hilt.
class MainActivity : AppCompatActivity(), ForegroundServiceConnection {
private lateinit var mService: ForegroundService
private var mBound: Boolean = false
private val mConnection = object : ServiceConnection {
override fun onServiceConnected(className: ComponentName, service: IBinder) {
Log.d("SERVICE CONNECTED", "FOREGROUND RUNNING")
val binder = service as ForegroundService.ForegroundServiceBinder
mService = binder.getService()
mBound = true
mService.setCallbacks(this#MainActivity)
}
override fun onServiceDisconnected(arg0: ComponentName){
mBound = false
}
}
}
You will also need to implement the binding in the service:
class ForegroundService: LifecycleService() {
private val binder = ForegroundServiceBinder()
inner class ForegroundServiceBinder : Binder() {
fun getService(): ForegroundService = this#ForegroundService
}
override fun onBind(intent: Intent): IBinder {
return binder
}
private lateinit var serviceConnection: ForegroundServiceConnection
}
Next, create an interface that defines the methods you want available to the foreground service, like this:
interface ForegroundServiceConnection {
fun getRepositorySingleton () : RepositoryClass
}
Now, go back to the activity you added the binding to, and implement the interface methods.
fun getRepositorySingleton () : RepositoryClass {
return repo
}
Inside the foreground service, create the setCallbacks method that will receive the activity/app as an argument, and hold a reference to it, like this:
fun setCallbacks(app: MainActivity) {
serviceConnection = app
// Now you have access to the MainActivity
val repo = serviceConnection.getRepositorySingleton()
// Done
}
Remember to check for null values if you attempt to use the serviceConnection outside of this block, it may not have been created yet.
Enjoy! :)

How can i inject a class that needs context into Broadcast Receiver with hilt?

So i have a class that i would like to inject into BroadcastReceiver.
Here is the class:
class SomeClass #Inject contructor(#ActivityContext private val ctx: Context){
fun doStuff(){...}
}
When i tried this i get this error: error: [Dagger/MissingBinding] #dagger.hilt.android.qualifiers.ActivityContext android.content.Context cannot be provided without an #Provides-annotated method.
#AndroidEntryPoint
class Broadcast: BroadcastReceiver() {
#Inject lateinit var someClass: SomeClass
override fun onReceive(context: Context?, intent: Intent?) {
someClass.doStuff()
}
}
I assume is a problem with the context of SomeClass because i tried it removing that parameter and it works.
#ActivityContext can only use in Activity lifecycle but instead of this you can use #ApplicationContext.
class SomeClass #Inject contructor(#ApplicationContext private val ctx: Context){
}
Create this class:
/**
* This class is created in order to be able to #Inject variables into Broadcast Receiver.
* Simply Inherit this class into whatever BroadcastReceiver you need and freely use Dagger-Hilt after.
*/
abstract class HiltBroadcastReceiver : BroadcastReceiver() {
#CallSuper
override fun onReceive(context: Context, intent: Intent?) {
}
}
Then, your BroadcastReceiver should look like this:
#AndroidEntryPoint
class BootReceiver : HiltBroadcastReceiver() {
// Injection -> Example from my application
#Inject
lateinit var internetDaysLeftAlarm: InternetDaysLeftAlarm
override fun onReceive(context: Context, intent: Intent?) {
super.onReceive(context, intent)
// Do whatever you need
}
}
Also, as some people already mentioned here, watch out for #ActivityContext. Instead, you should just use #ApplicationContext
Classes annotated with #ActivityContext can only be injected inside objects that are #ActivityScoped. So yes you are right, the context is the problem here.

Unresolved reference: method of extended class in Kotlin

I have a variable in my class that extends BroadcastReceiver and implements one additional method called isNetworkAvailable. When I call the method within the class it works but it results in "Unresolved reference" when called from outside. The preexisting methods of the class are also accessible.
Any ideas?
class MainActivity : AppCompatActivity() {
private var connectivityReceiver: BroadcastReceiver = object: BroadcastReceiver(){
override fun onReceive(context: Context, arg1: Intent) {
if ( isNetworkAvailable(this#MainActivity) ) { // Works just as it's supposed to.
// ...
}
}
fun isNetworkAvailable(context: Context?): Boolean {
// ...
}
}
override fun onCreate(savedInstanceState: Bundle?) {
// ...
connectivityReceiver.onReceive() // Accessible if arguments are provided
connectivityReceiver.isNetworkAvailable(this#MainActivity) // ERROR: Unresolved reference
}
}
#Fred answer is correct, it is because compiler don't know that the BroadcastReceiver has the function called isNetworkAvailable.
Kotlin is strong in type interference, you don't need to specify explicitly that the variable is of type BroadcastReceiver it tells the compiler that the object is of type BroadcastReceiver which does not have any function called isNetworkAvailable().
Just remove the explicit type declaration from the variable
private var connectivityReceiver = object: BroadcastReceiver() {...}
Kotlin will automatically assign the correct type using its inferred type of anonymous object.
You're variable connectivityReceiver is of type BroadcastReceiver, which has no method isNetworkAvailable. Unfortunately, it's not as simple as this because for Kotlin connectivityReceiver is nothing but a BroadcastReceiver.
To make your method available you can create a specific class and not an anonymous object.
class MyBroadcaseReceiver : BroadcastReceiver(){
override fun onReceive(context: Context, arg1: Intent) {
if ( isNetworkAvailable(this#MainActivity) ) { // Works just as it's supposed to.
// ...
}
}
fun isNetworkAvailable(context: Context?): Boolean {
// ...
}
}
Then in the activity just use
private var connectivityReceiver: MyBroadcaseReceiver = MyBroadcaseReceiver()
Note that if you do something like
private var connectivityReceiver: BroadcaseReceiver = MyBroadcaseReceiver()
you'll end up in the same issue since connectivityReceiver will be a BroadcastReceiver and not the class where isNetworkAvailable is defined.
There's also the possibility of just removing the explicit type:
private var connectivityReceiver = object: BroadcastReceiver(){
override fun onReceive(context: Context, arg1: Intent) {
if ( isNetworkAvailable(this#MainActivity) ) { // Works just as it's supposed to.
// ...
}
}
fun isNetworkAvailable(context: Context?): Boolean {
// ...
}
}
Kotlin's inference should be able to pick up the method then. The reason why I think this is not a great approach is because it only works if you want to keep this object expression private - see object expressions
Note that anonymous objects can be used as types only in local and private declarations. If you use an anonymous object as a return type of a public function or the type of a public property, the actual type of that function or property will be the declared supertype of the anonymous object, or Any if you didn't declare any supertype. Members added in the anonymous object will not be accessible.
Basically means in your case if you remove private the member isNetworkAvailable won't be accessible anymore. I believe code is meant to change a lot and especially a class like that eventually should go to its own place as it becomes more complex and makes it easier to test. This is of course just a personal opinion.

OnBootComplete Dagger 2 doesnt work

Hi i have the below broadcast reciever that retrieves a onboot complete to do some stuff but it fails saying no injector factory bound found for the reciever.
my guess is that dagger cant be initialised without the app launching manually by a user?
class BootReceiver : DaggerBroadcastReceiver(){
#Inject
lateinit var storageClearSchedular: StorageClearSchedularContract
override fun onReceive(context: Context?, intent: Intent?) {
super.onReceive(context, intent)
Log.d(BootReceiver::class.java.simpleName,"Starting up schedulers for clearing cache")
storageClearSchedular.setAllSchedulars()
}
}
error:
Caused by: java.lang.IllegalArgumentException: No injector factory bound for Class
There's no injector factory bound for your BroadcastReceiver because there's no component providing it. First you need to create a new module e.g. ReceiversBindingModule:
#Module
interface ReceiversBindingModule {
#ContributesAndroidInjector
fun provideBootReceiver(): BootReceiver
}
Then you need to add it to your AppComponent
#Component(modules = [
...
ReceiversBindingModule::class,
...])
class AppComponent

Default constructor for IntentService (kotlin)

I am new with Kotlin and little bit stack with intentService. Manifest shows me an error that my service doesn't contain default constructor, but inside service it looks ok and there are no errors.
Here is my intentService:
class MyService : IntentService {
constructor(name:String?) : super(name) {
}
override fun onCreate() {
super.onCreate()
}
override fun onHandleIntent(intent: Intent?) {
}
}
I was also try another variant:
class MyService(name: String?) : IntentService(name) {
but when I try to run this service I still get an error:
java.lang.Class<com.test.test.MyService> has no zero argument constructor
Any ideas how to fix default constructor in Kotlin?
Thanks!
As explained here your service class needs to have parameterless consturctor. Change your implementation to in example:
class MyService : IntentService("MyService") {
override fun onCreate() {
super.onCreate()
}
override fun onHandleIntent(intent: Intent?) {
}
}
The Android documentation on IntentService states that this name is only used for debugging:
name String: Used to name the worker thread, important only for debugging.
While not stated explicitly, on the mentioned documentation page, the framework needs to be able to instantiate your service class and expects there will be a parameterless constructor.

Categories

Resources