I'm learning android development from Android_developer. while using CoroutinesWorker, I encountered a problem whilst working with Kotlin
Logcat
com.google.samples.apps.devbyteviewer E/WM-WorkerFactory: Could
not instantiate
com.google.samples.apps.devbyteviewer.work.RefreshDataWorker
java.lang.reflect.InvocationTargetException
at java.lang.reflect.Constructor.newInstance0(Native Method)
at java.lang.reflect.Constructor.newInstance(Constructor.java:343)
at
androidx.work.WorkerFactory.createWorkerWithDefaultFallback(Worker
Factory.java:96)
at
androidx.work.impl.WorkerWrapper.runWorker(WorkerWrapper.java:
244)
at androidx.work.impl.WorkerWrapper.run(WorkerWrapper.java:136)
The Application class from where I instantiate workmanager.
Application class
package com.google.samples.apps.devbyteviewer
import android.app.Application
import androidx.work.*
import
com.google.samples.apps.devbyteviewer.work.RefreshDataWorker
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import timber.log.Timber
import java.util.concurrent.TimeUnit
As I have read in related posts, my worker class is a top level class and in an independent file.
Worker class
package com.google.samples.apps.devbyteviewer.work
import android.content.Context
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import com.google.samples.apps.devbyteviewer.database.getDatabase
import com.google.samples.apps.devbyteviewer.repository.VideosRepository
import retrofit2.HttpException
import timber.log.Timber
class RefreshDataWorker(appContext: Context, params: WorkerParameters) :
CoroutineWorker(appContext, params) {
override suspend fun doWork(): Result {
val database = getDatabase(applicationContext)
val repository = VideosRepository(database)
try {
repository.refreshVideos()
Timber.d("Work request for sync is run")
} catch (e: HttpException) {
return Result.retry()
}
return Result.success()
}
companion object {
/**
* Define a work name to uniquely identify this worker.
*/
const val WORK_NAME = "com.example.android.devbyteviewer.work.RefreshDataWorker"
}
}
I've had the same problem while working with WorkManager.
Pulling meaning from your post and the error message, this is very likely an import related issue. Check your Application dependencies.
Rather than:
implementation "android.arch.work:work-runtime-ktx:$work_version"
Use:
implementation "androidx.work:work-runtime-ktx:$work_version"
Related
How to call api NotificationServiceExtension Android and similar in IOS ?
when notification is received i want to check the condition based on api call response
package com.example.app
import android.content.Context import com.onesignal.OSNotificationReceivedEvent import com.onesignal.OneSignal import android.app.Notification import android.R import androidx.core.app.NotificationCompat import android.content.SharedPreferences import android.util.Log
class NotificationServiceExtension : OneSignal.OSRemoteNotificationReceivedHandler {
override fun remoteNotificationReceived( context: Context,notificationReceivedEvent: OSNotificationReceivedEvent
) {
val notification = notificationReceivedEvent.notification
val mutableNotification = notification.mutableCopy()
//call api here and check condition
notificationReceivedEvent.complete(mutableNotification)
} }
I am learning Kotlin to build a note app. I have created a repository class as shown below which takes a Dao parameter. For now, the source of data is just Dao but in the tutorial I am following, it calls an API class as well.
What I want to know is how do I test a repository classes logic?
import androidx.lifecycle.LiveData
import com.example.lastnotetakingapp.db.daos.NoteDao
import com.example.lastnotetakingapp.db.models.Note
class NotesRepo(private val notesDao: NoteDao) {
val allNotes: LiveData<List<Note>> = notesDao.getAllNotes()
suspend fun addNewNote(note: Note): Long {
return notesDao.addNewNote(note)
}
}
My test which is passing but it is 100% identical to the way a Dao would be tested except I use repo object, which made wonder if I am doing it right or not:
Is mocking the Database/DAO possible so I can spy on them to make sure they are called and all?
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import androidx.room.Room
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.example.lastnotetakingapp.db.NoteDB
import com.example.lastnotetakingapp.db.daos.NoteDao
import com.example.lastnotetakingapp.db.models.Note
import com.example.lastnotetakingapp.testHelpers.getOrAwaitValue
import com.google.common.truth.Truth
import kotlinx.coroutines.runBlocking
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
#RunWith(AndroidJUnit4::class)
class NotesRepoTest {
private lateinit var dao: NoteDao
private lateinit var db: NoteDB
private lateinit var notesRepo: NotesRepo
#get:Rule
var instantTaskExecutorRule = InstantTaskExecutorRule()
#Before
fun setUp(){
db = Room.inMemoryDatabaseBuilder(
ApplicationProvider.getApplicationContext(),
NoteDB::class.java,
).allowMainThreadQueries().build()
dao= db.noteDao
notesRepo = NotesRepo(dao)
}
#After
fun tearDown(){
db.close()
}
#Test
fun saveNotesTest(): Unit = runBlocking{
val note = Note(0, "tupac", "content", 0)
val id : Long = notesRepo.addNewNote(note)
Truth.assertThat(id).isEqualTo(1)
val notes = notesRepo.allNotes.getOrAwaitValue()
val noteOne: Note? = notes?.get(0)
Truth.assertThat(notes?.size).isEqualTo(1)
Truth.assertThat(noteOne?.title).isEqualTo(note.title)
Truth.assertThat(noteOne?.content).isEqualTo(note.content)
Truth.assertThat(noteOne?.viewed).isEqualTo(false)
}
}
I am currently trying to write an integration test for my repository layer that tests if I call a method, getExercises(), then it returns List<Exercise>, provided that the data is loaded into the local Firestore emulator ahead of time.
So far I got the local Firestore emulator to switch on and off at the beginning/end of a test run, respectively. I am able to populate my data into Firestore, and see the data in the local Firestore emulator via the web UI.
My problem is that my test assertion times out because the Task (an asynchronous construct the Firestore library uses), blocks the thread at the await() part in the repository method.
Test
package com.example.fitness.data
import androidx.test.ext.junit.runners.AndroidJUnit4
import app.cash.turbine.test
import com.example.fitness.Constants.EXERCISES_REF
import com.example.fitness.FirebaseEmulatorTest
import com.google.android.gms.tasks.Tasks
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import org.hamcrest.CoreMatchers.`is`
import org.hamcrest.MatcherAssert.assertThat
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import javax.inject.Inject
import kotlin.time.Duration
import kotlin.time.ExperimentalTime
#HiltAndroidTest
#RunWith(AndroidJUnit4::class)
class ExerciseRepositoryTest : FirebaseEmulatorTest() {
#get:Rule
var hiltRule = HiltAndroidRule(this)
#Inject
lateinit var subject: ExerciseRepository
#Before
fun setup() {
hiltRule.inject()
}
#ExperimentalTime
#Test
fun `#getExercises returns a flow of exercises`() = runBlocking {
val exercises = mutableListOf<Exercise>().apply {
add(Exercise("a", "pushups"))
add(Exercise("b", "pull-ups"))
add(Exercise("c", "sit-ups"))
}
runBlocking(Dispatchers.IO) {
val task1 = firestoreInstance.collection(EXERCISES_REF).add(exercises.first())
val task2 = firestoreInstance.collection(EXERCISES_REF).add(exercises[1])
val task3 = firestoreInstance.collection(EXERCISES_REF).add(exercises.last())
Tasks.await(task1)
Tasks.await(task2)
Tasks.await(task3)
println("Done with tasks: task1: ${task1.isComplete}. task2: ${task2.isComplete}. task3: ${task3.isComplete}.")
}
println("About to get exercises")
subject.getExercises().test(timeout = Duration.seconds(5)) {
println("test body")
assertThat(awaitItem().size, `is`(4)) // Just checking that it passes for the right reasons first. This number should be 3
}
}
}
Repository (System under test)
package com.example.fitness.data
import com.example.fitness.Constants.EXERCISES_REF
import com.google.firebase.firestore.CollectionReference
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.tasks.await
import javax.inject.Inject
import javax.inject.Named
import javax.inject.Singleton
#Singleton
class ExerciseRepository #Inject constructor(
#Named(EXERCISES_REF) private val exerciseCollRef: CollectionReference
) {
fun getExercises() = flow<List<Exercise>> {
println("beginning of searchForExercise")
val exercises = exerciseCollRef.limit(5).get().await() // NEVER FINISHES!!
println("Exercise count: ${exercises.documents}")
emit(exercises.toObjects(Exercise::class.java))
}
}
The output of this results in:
Done with tasks: task1: true. task2: true. task3: true.
About to search for exercises
beginning of searchForExercise
test body
Timed out waiting for 5000 ms
kotlinx.coroutines.TimeoutCancellationException: Timed out waiting for 5000 ms
"Exercise count: 3" message never prints!
Note: I am using Robolectric 4.6.1, kotlinx-coroutines-playservices (1.5.0) to provide the await() extension function, and the Turbine testing library for flow assertions (0.6.1)
Perhaps of relevance is a superclass this test inherits that sets the main dispatcher to a test dispatcher.
package com.example.fitness
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.test.*
import org.junit.After
import org.junit.Before
import org.junit.Rule
abstract class CoroutineTest {
#Rule
#JvmField
val rule = InstantTaskExecutorRule()
protected val testDispatcher: TestCoroutineDispatcher = TestCoroutineDispatcher()
private val testCoroutineScope = TestCoroutineScope(testDispatcher)
#Before
fun setupViewModelScope() {
Dispatchers.setMain(testDispatcher)
}
#After
fun cleanupViewModelScope() {
Dispatchers.resetMain()
}
#After
fun cleanupCoroutines() {
testDispatcher.cleanupTestCoroutines()
testDispatcher.resumeDispatcher()
}
fun runBlockingTest(block: suspend TestCoroutineScope.() -> Unit) =
testCoroutineScope.runBlockingTest(block)
}
Any help here would be greatly appreciate.
Edit
I have opened an issue with the kotlin extensions team to get more visibility on how to go about testing this, including a repo demonstrating the problem.
This problem has been resolved in a new version of the kotlinx-coroutines package (1.6.0-RC). See my github compare across branches. Tests now pass as expected with this version.
I am trying to set dagger up to provide a data module but I keep running into this error :
error: #Modules cannot be scoped. Did you mean to scope a method instead?
#javax.inject.Singleton()
^
Here is the culprit module :
package com.bottlerocket.dependancyinjection.modules
import android.content.Context
import androidx.room.Room
import com.bottlerocket.dependancyinjection.DI
import com.data.api.BottleRocketApi
import com.data.cache.StoreCache
import com.data.database.CacheDataStoreObject
import com.data.database.RemoteStoreDataObject
import com.data.database.StoreDatabase
import com.data.repository.StoreRepositoryImplementation
import dagger.Module
import dagger.Provides
import interfaces.StoresRepository
import javax.inject.Named
import javax.inject.Singleton
#Module
#Singleton
class DataModule {
#Singleton
#Provides
fun provideRoomDatabase(context: Context): StoreDatabase {
return Room.databaseBuilder(
context,
StoreDatabase::class.java,
"stores_db"
).build()
}
#Provides
#Singleton
fun provideStoreRepository(
api: BottleRocketApi,
#Named(DI.inMemoryCache) cache: StoreCache
): StoresRepository {
val cachedMoviesDataStore = CacheDataStoreObject(cache)
val remoteMoviesDataStore = RemoteStoreDataObject(api)
return StoreRepositoryImplementation(cachedMoviesDataStore, remoteMoviesDataStore)
}
}
As the error indicates, you can't annotate a module with #Singleton, only the functions. So all you have to do is remove the annotation from your module.
Change
#Module
#Singleton
class DataModule {
}
with
#Module
class DataModule {
}
This check was introduced in a recent version of Dagger.
I want to pre-populate my Room database from the json file in the assets folder. I follow the Google Sunflower sample. I copied the SeedDatabaseWorker class:
import android.content.Context
import android.util.Log
import androidx.work.Worker
import androidx.work.WorkerParameters
import com.dmitrysimakov.gymlab.data.GymLabDb
import com.dmitrysimakov.gymlab.data.entity.Training
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import com.google.gson.stream.JsonReader
import javax.inject.Inject
class SeedDatabaseWorker(val context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) {
private val TAG = SeedDatabaseWorker::class.java.simpleName
#Inject lateinit var database: GymLabDb
override fun doWork(): Worker.Result {
val plantType = object : TypeToken<List<Training>>() {}.type
var jsonReader: JsonReader? = null
return try {
val inputStream = context.assets.open("training.json")
jsonReader = JsonReader(inputStream.reader())
val plantList: List<Training> = Gson().fromJson(jsonReader, plantType)
database.trainingDao().insert(plantList)
Worker.Result.SUCCESS
} catch (ex: Exception) {
Log.e(TAG, "Error seeding database", ex)
Worker.Result.FAILURE
} finally {
jsonReader?.close()
}
}
}
I'm using Dagger 2, so instead of doing this: Sunflower AppDatabase, I do this:
import android.arch.persistence.db.SupportSQLiteDatabase
import android.arch.persistence.room.Room
import android.arch.persistence.room.RoomDatabase
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import com.dmitrysimakov.gymlab.GymLabApp
import com.dmitrysimakov.gymlab.data.GymLabDb
import com.dmitrysimakov.gymlab.workers.SeedDatabaseWorker
import dagger.Module
import dagger.Provides
import javax.inject.Singleton
#Module(includes = [ViewModelModule::class])
class AppModule {
#Singleton
#Provides
fun provideDb(app: GymLabApp): GymLabDb {
return Room
.databaseBuilder(app, GymLabDb::class.java, "gymlab.db")
.addCallback(object : RoomDatabase.Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
val request = OneTimeWorkRequestBuilder<SeedDatabaseWorker>().build()
WorkManager.getInstance().enqueue(request)
}
})
.fallbackToDestructiveMigration()
.build()
}
#Singleton
#Provides
fun provideTrainingDao(db: GymLabDb) = db.trainingDao()
}
But I can't inject the database that has not yet been created. So, how can I access the dao?
The problem was that I couldn't inject my database into the Worker. I found the solution here: AndroidWorkerInjection
Your issue is that SeedDatabaseWorker is still based on Worker() which is deprecated now, so you need to use Worker(Context, WorkerParameters) this constructor.
Check my answer from another post, it'll help you understand WorkManager library.
Edit :
You can now check Worker from that Sunflower demo, it's updated.