I have a problem when I want to switch between two activities.
This is my code on the first activity. I want to switch to the CatDoorActivity.
modifier = Modifier.clickable {
val intent = Intent(this, CatDoorActivity::class.java)
intent.putExtra("UUID", deviceUUID).putExtra("TITLE", deviceTitle)
this.startActivity(intent)
}
My Problem is this exception after running that code:
android.content.ActivityNotFoundException: Unable to find explicit activity class {com.example.mycatbellproject/com.example.mycatbellproject.CatDoorActivity}; have you declared this activity in your AndroidManifest.xml?
at android.app.Instrumentation.checkStartActivityResult(Instrumentation.java:2065)
at android.app.Instrumentation.execStartActivity(Instrumentation.java:1727)
at android.app.Activity.startActivityForResult(Activity.java:5314)
at androidx.activity.ComponentActivity.startActivityForResult(ComponentActivity.java:705)
at android.app.Activity.startActivityForResult(Activity.java:5272)
at androidx.activity.ComponentActivity.startActivityForResult(ComponentActivity.java:686)
at android.app.Activity.startActivity(Activity.java:5658)
at android.app.Activity.startActivity(Activity.java:5611)
at com.example.mycatbellproject.MainActivity$CardItem$1.invoke(MainActivity.kt:255)
at com.example.mycatbellproject.MainActivity$CardItem$1.invoke(MainActivity.kt:254)
at androidx.compose.foundation.ClickableKt$clickable$4$gesture$1$2.invoke-k-4lQ0M(Clickable.kt:153)
at androidx.compose.foundation.ClickableKt$clickable$4$gesture$1$2.invoke(Clickable.kt:142)
at androidx.compose.foundation.gestures.TapGestureDetectorKt$detectTapAndPress$2$1$1.invokeSuspend(TapGestureDetector.kt:223)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTaskKt.resume(DispatchedTask.kt:178)
at kotlinx.coroutines.DispatchedTaskKt.dispatch(DispatchedTask.kt:166)
at kotlinx.coroutines.CancellableContinuationImpl.dispatchResume(CancellableContinuationImpl.kt:397)
at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl(CancellableContinuationImpl.kt:431)
at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl$default(CancellableContinuationImpl.kt:420)
at kotlinx.coroutines.CancellableContinuationImpl.resumeWith(CancellableContinuationImpl.kt:328)
at androidx.compose.ui.input.pointer.SuspendingPointerInputFilter$PointerEventHandlerCoroutine.offerPointerEvent(SuspendingPointerInputFilter.kt:511)
at androidx.compose.ui.input.pointer.SuspendingPointerInputFilter.dispatchPointerEvent(SuspendingPointerInputFilter.kt:406)
at androidx.compose.ui.input.pointer.SuspendingPointerInputFilter.onPointerEvent-H0pRuoY(SuspendingPointerInputFilter.kt:419)
at androidx.compose.ui.input.pointer.Node.dispatchMainEventPass(HitPathTracker.kt:310)
at androidx.compose.ui.input.pointer.Node.dispatchMainEventPass(HitPathTracker.kt:297)
at androidx.compose.ui.input.pointer.Node.dispatchMainEventPass(HitPathTracker.kt:297)
at androidx.compose.ui.input.pointer.Node.dispatchMainEventPass(HitPathTracker.kt:297)
at androidx.compose.ui.input.pointer.Node.dispatchMainEventPass(HitPathTracker.kt:297)
at androidx.compose.ui.input.pointer.NodeParent.dispatchMainEventPass(HitPathTracker.kt:179)
at androidx.compose.ui.input.pointer.HitPathTracker.dispatchChanges(HitPathTracker.kt:98)
at androidx.compose.ui.input.pointer.PointerInputEventProcessor.process-BIzXfog(PointerInputEventProcessor.kt:80)
at androidx.compose.ui.platform.AndroidComposeView.sendMotionEvent-8iAsVTc(AndroidComposeView.android.kt:1205)
at androidx.compose.ui.platform.AndroidComposeView.handleMotionEvent-8iAsVTc(AndroidComposeView.android.kt:1155)
at androidx.compose.ui.platform.AndroidComposeView.dispatchTouchEvent(AndroidComposeView.android.kt:1095)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3118)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2799)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3118)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2799)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3118)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2799)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3118)
E/AndroidRuntime: at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2799)
at com.android.internal.policy.DecorView.superDispatchTouchEvent(DecorView.java:488)
at com.android.internal.policy.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1871)
at android.app.Activity.dispatchTouchEvent(Activity.java:4125)
at com.android.internal.policy.DecorView.dispatchTouchEvent(DecorView.java:446)
at android.view.View.dispatchPointerEvent(View.java:14568)
at android.view.ViewRootImpl$ViewPostImeInputStage.processPointerEvent(ViewRootImpl.java:6016)
at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:5819)
at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:5310)
at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:5367)
at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:5333)
at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:5485)
at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:5341)
at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:5542)
at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:5314)
at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:5367)
at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:5333)
at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:5341)
at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:5314)
at android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:8080)
at android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:8031)
at android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:7992)
at android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent(ViewRootImpl.java:8203)
at android.view.InputEventReceiver.dispatchInputEvent(InputEventReceiver.java:220)
at android.os.MessageQueue.nativePollOnce(Native Method)
at android.os.MessageQueue.next(MessageQueue.java:335)
at android.os.Looper.loop(Looper.java:183)
at android.app.ActivityThread.main(ActivityThread.java:7656)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
Suppressed: kotlinx.coroutines.DiagnosticCoroutineContextException: [androidx.compose.runtime.BroadcastFrameClock#f19bf6c, StandaloneCoroutine{Cancelling}#fd19935, AndroidUiDispatcher#cea91ca]
I/Process: Sending signal. PID: 7109 SIG: 9
I have added the activity to the AndroidManifest.xml:
<activity
android:name=".CatDoorActivity"
android:configChanges="orientation"
android:exported="false"
android:theme="#style/Theme.MyCatbellProject"/>
The bottom of the exception says something about coroutines. I use them in for my room database. Do I get a problem intending a new activity and switching to it while using coroutines in my room database in the first activity? I use the room database viewmodel in my first activity but I dont use that on my second activity.
That is my second activity so far:
class CatDoorActivity : ComponentActivity(){
private var uuid: String = ""
private var title: String = ""
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if(intent.extras != null) {
uuid = intent.extras!!.get("UUID") as String
title = intent.extras!!.get("TITLE") as String
}
setContent {
MyCatbellProjectTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
MenuScreenUI()
}
}
}
}
...
The Repository of my room db uses the coroutines:
class DeviceRepository(application: Application) {
val searchResults = MutableLiveData<List<Device>>()
private var deviceDao: DeviceDao?
private val coroutineScope = CoroutineScope(Dispatchers.Main)
val allDevices: LiveData<List<Device>>?
fun insertDevice(newDevice: Device) {
coroutineScope.launch(Dispatchers.IO) {
asyncInsert(newDevice)
}
}
private suspend fun asyncInsert(device: Device) {
deviceDao?.addDevice(device)
}
fun deleteDevice(id: Int) {
coroutineScope.launch(Dispatchers.IO) {
asyncDelete(id)
}
}
private suspend fun asyncDelete(id: Int) {
deviceDao?.deleteDevice(id)
}
fun findDevice(name: String) {
coroutineScope.launch(Dispatchers.Main) {
searchResults.value = asyncFind(name).await()
}
}
private suspend fun asyncFind(name: String): Deferred<List<Device>?> =
coroutineScope.async(Dispatchers.IO) {
return#async deviceDao?.findDevice(name)
}
init {
val db: DeviceRoomDatabase? = DeviceRoomDatabase.getDatabse(application)
deviceDao = db?.deviceDao()
allDevices = deviceDao?.getAllDevices()
}
}
Related
I build two apps with the same package name:
In the first app, I use SQLiteCipher using "Pass_Phrase" (Version_1 DB) for CRUD operations using Java.
In the second app, I just wanted to read data (without making a DBHelper class) from SQLiteCipher and put it into Room (Version_2 DB) while Migrating using Kotlin.
Note: Changes would be affected on App Update.
I completed the first app and was stuck at sqliteCipherDataList() in the second app. Need your help. Thanks a lot!
Code: UserDB.kt
#Database(entities = [User::class], version = 2, exportSchema = false)
abstract class UserDB : RoomDatabase() {
abstract fun userDAO(): UserDAO
companion object {
private var instance: UserDB? = null
private val MIGRATION_1_2: Migration = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
val db = SQLiteDatabase.openOrCreateDatabase("User.db", passphrase, null)
db.rawExecSQL("ATTACH DATABASE '${encryptedDbPath}' AS encrypted KEY '${passphrase}'")
db.rawExecSQL("SELECT sqlcipher_export('encrypted')")
db.rawExecSQL("DETACH DATABASE encrypted")
db.close()
val version = database.version
Log.v("Old version", version.toString() + "")
}
}
// return userList
fun sqliteCipherDataList(): List<User> {
val userList: MutableList<User> = ArrayList()
val db = SQLiteDatabase.openDatabase(encryptedDbPath, passphrase, null, SQLiteDatabase.OPEN_READONLY)
val cursor: Cursor = db.query("Contacts", null, null, null, null, null, null)
if (cursor.moveToFirst()) {
do {
#SuppressLint("Range")
val emails = User(cursor.getString(cursor.getColumnIndex("Email")))
userList.add(emails)
} while (cursor.moveToNext())
}
cursor.close()
db.close()
return userList
}
fun getDatabase(context: Context): UserDB? {
if (instance == null) {
synchronized(this) {
Log.d("UserDB", "Creating Database")
instance = Room.databaseBuilder(
context.applicationContext,
UserDB::class.java, "RoomDB"
)
.addMigrations(MIGRATION_1_2)
.allowMainThreadQueries()
.build()
}
}
return instance!!
}
}
}
Code: MainAcitvity.kt
class MainActivity : AppCompatActivity() {
private var userDB: UserDB? = null
private var userList: List<User>? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
SQLiteDatabase.loadLibs(this)
val roomTextView = findViewById<TextView>(R.id.roomText)
userList = UserDB.sqliteCipherDataList()
Log.d("SQLiteCipherData", userList.toString())
userDB = UserDB.getDatabase(this)
userDB?.userDAO()!!.insertProductListToRoom(userList)
val u: User = userDB?.userDAO()!!.getProductFromRoom()[0]
Log.v("RoomDBData", u.Email)
roomTextView.text = u.Email
}
}
Exception:
2022-07-22 16:24:21.085 18889-18889/com.example.sqlitecipherjava E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.sqlitecipherjava, PID: 18889
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.sqlitecipherjava/com.example.sqlitecipherjava.MainActivity}: net.sqlcipher.database.SQLiteDiskIOException: error code 10: Could not open database
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3114)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3257)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1948)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7050)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:494)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:965)
Caused by: net.sqlcipher.database.SQLiteDiskIOException: error code 10: Could not open database
at net.sqlcipher.database.SQLiteDatabase.dbopen(Native Method)
at net.sqlcipher.database.SQLiteDatabase.openDatabaseInternal(SQLiteDatabase.java:2412)
at net.sqlcipher.database.SQLiteDatabase.openDatabase(SQLiteDatabase.java:1149)
at net.sqlcipher.database.SQLiteDatabase.openDatabase(SQLiteDatabase.java:1116)
at net.sqlcipher.database.SQLiteDatabase.openDatabase(SQLiteDatabase.java:1065)
at net.sqlcipher.database.SQLiteDatabase.openDatabase(SQLiteDatabase.java:1019)
at com.example.sqlitecipherjava.UserDB$Companion.sqliteCipherDataList(UserDB.kt:41)
at com.example.sqlitecipherjava.MainActivity.onCreate(MainActivity.kt:22)
at android.app.Activity.performCreate(Activity.java:7327)
at android.app.Activity.performCreate(Activity.java:7318)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1271)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3094)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3257)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1948)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7050)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:494)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:965)
I've got an activity which uses camera intent. If I take a picture vertically and accept to display it in an imageview vertically there is no problem. If i take a picture horizontally and accept it while having the mobile horizontally it crashes, but if I turn it to vertical it works. Does anybody know why this might be happening?
It does not have much sense cause when the app crashes the only thing it says is that the photoFile is null, it seems as if there was no picture but in fact there is a picture.
Here is the code from the activity:
private const val FILE_NAME = "photo.jpg"
class TextCameraActivity : AppCompatActivity(), TextToSpeech.OnInitListener {
private lateinit var binding: ActivityTextCameraBinding
private lateinit var bitmap : Bitmap
private lateinit var photoFile: File
private var tts: TextToSpeech? = null
private var locale : Locale = Locale("es", "ES")
private var progressBar : ProgressBar? = null
private var i = 0
private val handler = Handler()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityTextCameraBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.btn7.setOnClickListener {
val takePictureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
photoFile = getPhotoFile(FILE_NAME)
val fileProvider = FileProvider.getUriForFile(this, "com.example.fileprovider", photoFile)
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, fileProvider)
if(takePictureIntent.resolveActivity(this.packageManager) != null) {
startActivityForResult(takePictureIntent, 42)
} else {
Toast.makeText(this, "Unable to open camera", Toast.LENGTH_SHORT).show()
Log.d("mensaje", "?????Unable to open camera")
}
}
tts = TextToSpeech(this, this)
progressBar = binding.progressBar
binding.btnReconocerImagen.setOnClickListener {
progressBar!!.visibility = View.VISIBLE
i = progressBar!!.progress
Thread(Runnable {
while (i < 10) {
i += 1
handler.post(Runnable {
progressBar!!.progress = i
})
try {
Thread.sleep(100)
} catch (e: InterruptedException) {
e.printStackTrace()
}
}
progressBar!!.visibility = View.INVISIBLE
}).start()
recognizeText()
}
val actionBar = supportActionBar
actionBar?.setDisplayHomeAsUpEnabled(true)
}
private fun getPhotoFile(fileName:String):File {
val storageDirectory = getExternalFilesDir(Environment.DIRECTORY_PICTURES)
return File.createTempFile(fileName, ".jpg", storageDirectory)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.getItemId()) {
android.R.id.home -> {
finish()
return true
}
}
return super.onOptionsItemSelected(item)
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
return true
}
override fun onInit(status: Int) {
if (status == TextToSpeech.SUCCESS) {
// set US English as language for tts
val result = tts!!.setLanguage(locale)
if (result == TextToSpeech.LANG_MISSING_DATA || result == TextToSpeech.LANG_NOT_SUPPORTED) {
Log.e("TTS","The Language specified is not supported!")
} else {
binding.btnReconocerImagen.isEnabled = true
}
} else {
Log.e("TTS", "Initilization Failed!")
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if(requestCode == 42 && resultCode == Activity.RESULT_OK) {
bitmap = BitmapFactory.decodeFile(photoFile.absolutePath)
binding.imageView.setImageBitmap(bitmap)
} else {
super.onActivityResult(requestCode, resultCode, data)
}
}
private fun recognizeText() {
try {
val image = InputImage.fromBitmap(bitmap, 0)
val recognizer = TextRecognition.getClient(TextRecognizerOptions.DEFAULT_OPTIONS)
val result = recognizer.process(image)
.addOnSuccessListener { visionText ->
for (block in visionText.textBlocks) {
val boundingBox = block.boundingBox
val cornerPoints = block.cornerPoints
val text = block.text
Log.d("mensaje", "he encontrado $text")
binding.tvTextoReconocido.text = "El texto reconocido es: $text"
tts!!.speak("El texto reconocido es $text", TextToSpeech.QUEUE_FLUSH, null, "")
for (line in block.lines) {
// ...
for (element in line.elements) {
// ...
}
}
}
}
.addOnFailureListener { e ->
}
} catch (e : Exception) {
Log.d("mensaje", "NO HAY IMAGEN SELECCIONADA")
Toast.makeText(this,"NO HAS SELECCIONADO NINGUNA IMAGEN PARA RECONOCER", Toast.LENGTH_SHORT).show()
}
}
}
Error:
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.appdigitalinkrecognition, PID: 30407
java.lang.RuntimeException: Unable to resume activity {com.example.appdigitalinkrecognition/com.example.appdigitalinkrecognition.TextCameraActivity}: java.lang.RuntimeException: Failure delivering result ResultInfo{who=null, request=42, result=-1, data=null} to activity {com.example.appdigitalinkrecognition/com.example.appdigitalinkrecognition.TextCameraActivity}: kotlin.UninitializedPropertyAccessException: lateinit property photoFile has not been initialized
at android.app.ActivityThread.performResumeActivity(ActivityThread.java:4918)
at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:4955)
at android.app.servertransaction.ResumeActivityItem.execute(ResumeActivityItem.java:52)
at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:176)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:97)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2336)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:246)
at android.app.ActivityThread.main(ActivityThread.java:8653)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:602)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1130)
Caused by: java.lang.RuntimeException: Failure delivering result ResultInfo{who=null, request=42, result=-1, data=null} to activity {com.example.appdigitalinkrecognition/com.example.appdigitalinkrecognition.TextCameraActivity}: kotlin.UninitializedPropertyAccessException: lateinit property photoFile has not been initialized
at android.app.ActivityThread.deliverResults(ActivityThread.java:5590)
at android.app.ActivityThread.performResumeActivity(ActivityThread.java:4905)
at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:4955)
at android.app.servertransaction.ResumeActivityItem.execute(ResumeActivityItem.java:52)
at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:176)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:97)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2336)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:246)
at android.app.ActivityThread.main(ActivityThread.java:8653)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:602)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1130)
Caused by: kotlin.UninitializedPropertyAccessException: lateinit property photoFile has not been initialized
at com.example.appdigitalinkrecognition.TextCameraActivity.onActivityResult(TextCameraActivity.kt:143)
at android.app.Activity.dispatchActivityResult(Activity.java:8550)
at android.app.ActivityThread.deliverResults(ActivityThread.java:5583)
at android.app.ActivityThread.performResumeActivity(ActivityThread.java:4905)
at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:4955)
at android.app.servertransaction.ResumeActivityItem.execute(ResumeActivityItem.java:52)
at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:176)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:97)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2336)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:246)
at android.app.ActivityThread.main(ActivityThread.java:8653)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:602)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1130)
````
If I had to guess, you're getting different results because changing the orientation destroys an Activity, so a new one can be created to match the new orientation. So maybe that's happening in the background - but if you take a photo vertically, there's no orientation change so the Activity isn't destroyed
That's an issue because like the docs say:
Note: Since your process and activity can be destroyed between when you call launch() and when the onActivityResult() callback is triggered, any additional state needed to handle the result must be saved and restored separately from these APIs.
This is the closest thing I can find to them explicitly saying "onActivityResult can run before onCreate", but if that's what's happening to you, then that's why you're getting that lateinit property photoFile has not been initialized error.
It might also be because you're not actually initialising it in onCreate, you're doing it in a click listener - so even if onCreate does run first, the button would need to be clicked again, and onActivityResult definitely runs before that. But just moving it into onCreate might not be enough
I think to handle this properly, you'll need to think about your "show the photo" task having two parts - creating the path (in onCreate) and getting a result (through onActivityResult). You can't guarantee the order of those two things, so when you do one, you'll have to check if the other part has already been completed - if it has, you can show the image.
You could do something like this:
private var pathInitialised = false
private var photoTaken = false
override fun onCreate(savedInstanceState: Bundle?) {
...
// don't do this in the click listener - it needs to be available when the activity starts
photoFile = getPhotoFile(FILE_NAME)
// set the flag, and try to show the pic if ready
pathInitialised = true
tryDisplayPhoto()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if(requestCode == 42 && resultCode == Activity.RESULT_OK) {
// set the flag, and try to show the pic if ready
photoTaken = true
tryDisplayPhoto()
} else {
super.onActivityResult(requestCode, resultCode, data)
}
}
private fun tryDisplayPhoto() {
if (pathInitialised && photoTaken) {
// now you know that the path is available and a photo needs to be shown
bitmap = BitmapFactory.decodeFile(photoFile.absolutePath)
binding.imageView.setImageBitmap(bitmap)
}
}
So now, no matter what order those two methods are called, they both try to display a pic - and that will only happen when the last of those methods is called, and all the pieces are in place
Declaring photoFile as optional should solve the problem:
private var photoFile: File? = null
You will need to add a null-check in your onActivityResult:
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if(requestCode == 42 && resultCode == Activity.RESULT_OK) {
photoFile?.let { photo ->
bitmap = BitmapFactory.decodeFile(photo.absolutePath)
binding.imageView.setImageBitmap(bitmap)
} ?: run {
// Photo is not defined...
}
} else {
super.onActivityResult(requestCode, resultCode, data)
}
}
i want to update text of progressbar and i have this error
i tried but not work anything
some advice?
why i cant update text ?
Thanks!
2021-10-26 22:31:00.555 6192-6227/? E/AndroidRuntime: FATAL EXCEPTION: Timer-0
Process: com.example.myapplication, PID: 6192
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:8798)
at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1606)
at android.view.View.requestLayout(View.java:25390)
at android.view.View.requestLayout(View.java:25390)
at android.view.View.requestLayout(View.java:25390)
at android.view.View.requestLayout(View.java:25390)
at android.view.View.requestLayout(View.java:25390)
at android.view.View.requestLayout(View.java:25390)
at androidx.constraintlayout.widget.ConstraintLayout.requestLayout(ConstraintLayout.java:3593)
at android.view.View.requestLayout(View.java:25390)
at androidx.constraintlayout.widget.ConstraintLayout.requestLayout(ConstraintLayout.java:3593)
at android.view.View.requestLayout(View.java:25390)
at android.widget.TextView.checkForRelayout(TextView.java:9719)
at android.widget.TextView.setText(TextView.java:6311)
at android.widget.TextView.setText(TextView.java:6139)
at android.widget.TextView.setText(TextView.java:6091)
at com.example.myapplication.MainActivity$onCreate$1$1$onResponse$2$1.run(MainActivity.kt:84)
at java.util.TimerThread.mainLoop(Timer.java:562)
at java.util.TimerThread.run(Timer.java:512)
class MainActivity() : AppCompatActivity() {
private lateinit var textViewTur: TextView
companion object myCompanion {
var test8: Int = 0
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
textViewTur = findViewById(R.id.textTur)
val url = "http://blynk-cloud.com/b3Uq-vB64Hz1D_X3AJ506Q9OwmQLwha7/get/V5"
val request = Request.Builder().url(url).build()
val client = OkHttpClient()
with(client) {
newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
println("faillllllllllllllllllllllll")
}
override fun onResponse(call: Call, response: Response) {
val body = response.body?.string()
var test = body?.replace("[", "")?.replace("]", "")?.replace("\"", "")
val tboiler = findViewById<CircularProgressBar>(R.id.PB_Tb)
tboiler.apply {
val timer = Timer()
timer.schedule(
object : TimerTask() {
override fun run() {
progress = test?.toFloat()!!
myCompanion.test8 = progress.toInt()
textViewTur.text = progress.toString()
}
},
0,
2000
)
}
}
})
}
}
The Timer class runs its code on a new thread, but you can only update UI elements like TextView from the main thread.
You can use postDelayed on a view to tell it to run code on the main thread after some delay, for example:
tBoiler.apply {
postDelayed(2000L, {
progress = test?.toFloat()!!
myCompanion.test8 = progress.toInt()
textViewTur.text = progress.toString()
})
}
This will not run the code repeatedly. If you want to do that, you could move the delayed action into a separate function that can recursively call itself, and call the function to start the loop. You need to store the runnable in a property if you want to be able to stop the loop.
private val progressRunnable = Runnable {
tBoiler.apply {
progress = test?.toFloat()!!
myCompanion.test8 = progress.toInt()
textViewTur.text = progress.toString()
loopProgress()
}
}
private fun loopProgress() {
tBoiler.postDelayed(2000L, progressRunnable)
}
private fun stopLoopProgress {
tBoiler.removeCallbacks(progressRunnable)
}
Alternatively, your code could be simplified with a coroutine:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
textViewTur = findViewById(R.id.textTur)
lifecycleScope.launch {
val url = "http://blynk-cloud.com/b3Uq-vB64Hz1D_X3AJ506Q9OwmQLwha7/get/V5"
val request = Request.Builder().url(url).build()
val client = OkHttpClient()
val response = try {
client.newCall(request)
} catch (e: IOException) {
println("faillllllllllllllllllllllll")
return#launch
}
val test = response.body?.string()?.filter { it.isDigit() || it == '.' }
.toFloatOrNull()
if (test == null) {
println("body cannot be parsed as Float: ${response.body}")
return#launch
}
val tboiler = findViewById<CircularProgressBar>(R.id.PB_Tb)
while (true) {
delay(2000L)
tboiler.apply {
progress = test
myCompanion.test8 = progress.toInt()
textViewTur.text = progress.toString()
}
}
}
}
// You can put this in a file for http utility functions
public suspend fun Call.await(): Response = suspendCancellableCoroutine { cont ->
enqueue(object : Callback {
override fun onResponse(call: Call, response: Response) {
cont.resume(response)
}
override fun onFailure(call: Call, e: IOException) {
cont.resumeWithException(e)
}
})
cont.invokeOnCancellation {
runCatching { cancel() }
}
}
I am developing an Android chat app with Paging library 3 androidx.paging:paging-runtime-ktx:3.0.0-alpha12
The paging library retrieves chat messages from the Room database.
When I insert a new chat message to the ChatMessage table in the Room database, while scrolling recycler view of chat message, it throws an exception as per GIF below. The send button just inserts a new item into the ChatMessage table.
This is the error logs.
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.beeswork.balance, PID: 24171
java.lang.UnsupportedOperationException: Stable ids are unsupported on PagingDataAdapter.
at androidx.paging.PagingDataAdapter.setHasStableIds(PagingDataAdapter.kt:127)
at com.beeswork.balance.ui.chat.ChatFragment.setupChatRecyclerView(ChatFragment.kt:71)
at com.beeswork.balance.ui.chat.ChatFragment.access$setupChatRecyclerView(ChatFragment.kt:24)
at com.beeswork.balance.ui.chat.ChatFragment$bindUI$1.invokeSuspend(ChatFragment.kt:58)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at android.os.Handler.handleCallback(Handler.java:751)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6077)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:866)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:756)
Disconnected from the target VM, address: 'localhost', transport: 'socket'
This is the code to setup the chat recycler view in Chat fragment
private fun setupChatRecyclerView() {
chatPagingAdapter = ChatPagingAdapter()
rvChat.adapter = chatPagingAdapter
val layoutManager = LinearLayoutManager(this#ChatFragment.context)
layoutManager.orientation = LinearLayoutManager.VERTICAL
layoutManager.reverseLayout = true
rvChat.layoutManager = layoutManager
lifecycleScope.launch {
viewModel.chatMessages.collectLatest {
chatPagingAdapter.submitData(it)
}
}
}
This is the chatMessages in ChatViewModel
val chatMessages = Pager(
PagingConfig(
pageSize = 30,
enablePlaceholders = false,
maxSize = 150
)
) {
balanceRepository.getChatMessages(chatId)
}.flow.cachedIn(viewModelScope)
This is the query in ChatDAO
#Query("select * from chatMessage where chatId = :chatId order by case when id is null then 0 else 1 end, id desc, messageId desc")
fun getChatMessages(chatId: Long): PagingSource<Int, ChatMessage>
This is the ChatPagingAdapter extends PagingDataAdapter
class ChatPagingAdapter : PagingDataAdapter<ChatMessage, ChatPagingAdapter.MessageViewHolder>(
diffCallback
) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MessageViewHolder {
return when (viewType) {
ChatMessage.Status.RECEIVED.ordinal -> MessageViewHolder(parent.inflate(R.layout.item_chat_message_received))
ChatMessage.Status.SENT.ordinal -> MessageViewHolder(parent.inflate(R.layout.item_chat_message_sent))
else -> MessageViewHolder(parent.inflate(R.layout.item_chat_message_received))
}
}
override fun onBindViewHolder(holder: MessageViewHolder, position: Int) {
getItem(position)?.let {
when (holder.itemViewType) {
ChatMessage.Status.RECEIVED.ordinal -> holder.bindMessageReceived(it)
ChatMessage.Status.SENT.ordinal -> holder.bindMessageSent(it)
}
}
}
override fun getItemViewType(position: Int): Int {
return getItem(position)?.let {
return if (it.status == ChatMessage.Status.RECEIVED) ChatMessage.Status.RECEIVED.ordinal else ChatMessage.Status.SENT.ordinal
} ?: kotlin.run {
return ChatMessage.Status.RECEIVED.ordinal
}
}
companion object {
private val diffCallback = object : DiffUtil.ItemCallback<ChatMessage>() {
override fun areItemsTheSame(oldItem: ChatMessage, newItem: ChatMessage): Boolean =
oldItem.id == newItem.id
override fun areContentsTheSame(oldItem: ChatMessage, newItem: ChatMessage): Boolean =
oldItem == newItem
}
}
class MessageViewHolder(
itemView: View
) : RecyclerView.ViewHolder(itemView) {
fun bind {...}
}
}
Here are questions
Can I keep my scrolling while inserting a new item to chat message table please?
If not possible, then how can I resolve the exception please?
I am using Koin as depenedency Injection framework in my Kotlin Application. I am trying to save data to Room database using coroutines. I have a usecase "AddToFavourite" class which was calling from viewmodel. While running the app the app crash with below error. When I check I understand that koin could not find some dependency. Can any one please help me out with a solution. Please find the repository, usecase, viewmodel, database and di modules as below
Process: com.debin.pokemonsearch, PID: 16027
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.debin.pokemonsearch/com.debin.pokemonsearch.HomeActivity}: org.koin.core.error.InstanceCreationException: Could not create instance for [Factory:'com.debin.pokemonsearch.presentation.search.SearchViewModel']
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2817`
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2892)
at android.app.ActivityThread.-wrap11(Unknown Source:0)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1593)
at android.os.Handler.dispatchMessage(Handler.java:105)
at android.os.Looper.loop(Looper.java:164)
at android.app.ActivityThread.main(ActivityThread.java:6541)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767)
Caused by: org.koin.core.error.InstanceCreationException: Could not create instance for [Factory:'com.debin.pokemonsearch.presentation.search.SearchViewModel']
at org.koin.core.instance.InstanceFactory.create(InstanceFactory.kt:59)
at org.koin.core.instance.FactoryInstanceFactory.get(FactoryInstanceFactory.kt:36)
at org.koin.core.registry.InstanceRegistry.resolveInstance$koin_core(InstanceRegistry.kt:87)
at org.koin.core.scope.Scope.resolveInstance(Scope.kt:214)
at org.koin.core.scope.Scope.get(Scope.kt:181)
at org.koin.android.viewmodel.ViewModelFactoryKt$defaultViewModelFactory$1.create(ViewModelFactory.kt:13)
at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:187)
at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:150)
at org.koin.android.viewmodel.ViewModelResolutionKt.get(ViewModelResolution.kt:21)
at org.koin.android.viewmodel.ViewModelResolutionKt.resolveInstance(ViewModelResolution.kt:10)
at org.koin.android.viewmodel.scope.ScopeExtKt.getViewModel(ScopeExt.kt:68)
at org.koin.android.viewmodel.scope.ScopeExtKt.getViewModel(ScopeExt.kt:56)
at org.koin.android.viewmodel.koin.KoinExtKt.getViewModel(KoinExt.kt:34)
at org.koin.android.viewmodel.ext.android.ViewModelStoreOwnerExtKt.getViewModel(ViewModelStoreOwnerExt.kt:66)
at com.debin.pokemonsearch.presentation.search.SearchFragment$$special$$inlined$viewModel$1.invoke(ViewModelStoreOwnerExt.kt:71)
at com.debin.pokemonsearch.presentation.search.SearchFragment$$special$$inlined$viewModel$1.invoke(Unknown Source:0)
at kotlin.UnsafeLazyImpl.getValue(Lazy.kt:81)
at com.debin.pokemonsearch.presentation.search.SearchFragment.getViewModel(Unknown Source:2)
at com.debin.pokemonsearch.presentation.search.SearchFragment.observePokemon(SearchFragment.kt:39)
at com.debin.pokemonsearch.presentation.search.SearchFragment.onViewCreated(SearchFragment.kt:31)
at androidx.fragment.app.FragmentStateManager.createView(FragmentStateManager.java:332)
at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1187)
at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1356)
at androidx.fragment.app.FragmentManager.moveFragmentToExpectedState(FragmentManager.java:1434)
at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1497)
at androidx.fragment.app.FragmentManager.dispatchStateChange(FragmentManager.java:2625)
at androidx.fragment.app.FragmentManager.dispatchActivityCreated(FragmentManager.java:2577)
at androidx.fragment.app.Fragment.performActivityCreated(Fragment.java:2722)
at androidx.fragment.app.FragmentStateManager.activityCreated(FragmentStateManager.java:346)
at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1188)
at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1356)
at androidx.fragment.app.FragmentManager.moveFragmentToExpectedState(FragmentManager.java:1434)
at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1497)
2021-01-24 19:49:54.088 16027-16027/com.debin.pokemonsearch E/AndroidRuntime: at androidx.fragment.app.FragmentManager.dispatchStateChange(FragmentManager.java:2625)
at androidx.fragment.app.FragmentManager.dispatchActivityCreated(FragmentManager.java:2577)
at androidx.fragment.app.FragmentController.dispatchActivityCreated(FragmentController.java:247)
at androidx.fragment.app.FragmentActivity.onStart(FragmentActivity.java:541)
at androidx.appcompat.app.AppCompatActivity.onStart(AppCompatActivity.java:210)
at android.app.Instrumentation.callActivityOnStart(Instrumentation.java:1333)
at android.app.Activity.performStart(Activity.java:6992)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2780)
... 9 more
Caused by: org.koin.core.error.InstanceCreationException: Could not create instance for [Factory:'com.debin.pokemonsearch.pokemoncore.interactors.AddToFavourites']
at org.koin.core.instance.InstanceFactory.create(InstanceFactory.kt:59)
at org.koin.core.instance.FactoryInstanceFactory.get(FactoryInstanceFactory.kt:36)
at org.koin.core.registry.InstanceRegistry.resolveInstance$koin_core(InstanceRegistry.kt:87)
at org.koin.core.scope.Scope.resolveInstance(Scope.kt:214)
at org.koin.core.scope.Scope.get(Scope.kt:181)
at com.debin.pokemonsearch.di.ViewModelModuleKt$viewModelModule$1$1.invoke(ViewModelModule.kt:24)
at com.debin.pokemonsearch.di.ViewModelModuleKt$viewModelModule$1$1.invoke(Unknown Source:4)
at org.koin.core.instance.InstanceFactory.create(InstanceFactory.kt:50)
... 49 more
Caused by: org.koin.core.error.InstanceCreationException: Could not create instance for [Single:'com.debin.pokemonsearch.pokemoncore.domain.repository.IPokemonCoreRepository']
at org.koin.core.instance.InstanceFactory.create(InstanceFactory.kt:59)
at org.koin.core.instance.SingleInstanceFactory.create(SingleInstanceFactory.kt:40)
at org.koin.core.instance.SingleInstanceFactory.get(SingleInstanceFactory.kt:48)
at org.koin.core.registry.InstanceRegistry.resolveInstance$koin_core(InstanceRegistry.kt:87)
at org.koin.core.scope.Scope.resolveInstance(Scope.kt:214)
at org.koin.core.scope.Scope.get(Scope.kt:181)
at com.debin.pokemonsearch.di.UseCaseModuleKt$useCaseModule$1$3.invoke(UseCaseModule.kt:23)
at com.debin.pokemonsearch.di.UseCaseModuleKt$useCaseModule$1$3.invoke(Unknown Source:4)
at org.koin.core.instance.InstanceFactory.create(InstanceFactory.kt:50)
... 56 more
Caused by: org.koin.core.error.InstanceCreationException: Could not create instance for [Single:'com.debin.pokemonsearch.pokemoncore.data.datasource.PokemonCoreDataSource']
at org.koin.core.instance.InstanceFactory.create(InstanceFactory.kt:59)
at org.koin.core.instance.SingleInstanceFactory.create(SingleInstanceFactory.kt:40)
at org.koin.core.instance.SingleInstanceFactory.get(SingleInstanceFactory.kt:48)
at org.koin.core.registry.InstanceRegistry.resolveInstance$koin_core(InstanceRegistry.kt:87)
at org.koin.core.scope.Scope.resolveInstance(Scope.kt:214)
at org.koin.core.scope.Scope.get(Scope.kt:181)
at com.debin.pokemonsearch.di.RepositoryModuleKt$repositoryModule$1$3.invoke(RepositoryModule.kt:19)
at com.debin.pokemonsearch.di.RepositoryModuleKt$repositoryModule$1$3.invoke(Unknown Source:4)
at org.koin.core.instance.InstanceFactory.create(InstanceFactory.kt:50)
... 64 more
Caused by: org.koin.core.error.NoBeanDefFoundException: No definition found for class:'com.debin.pokemonsearch.framework.db.PokemonDatabase'. Check your definitions!
at org.koin.core.scope.Scope.throwDefinitionNotFound(Scope.kt:246)
at org.koin.core.scope.Scope.resolveInstance(Scope.kt:216)
at org.koin.core.scope.Scope.get(Scope.kt:181)
at com.debin.pokemonsearch.di.DataSourceModuleKt$dataSourceModule$1$3.invoke(DataSourceModule.kt:19)
at com.debin.pokemonsearch.di.DataSourceModuleKt$dataSourceModule$1$3.invoke(Unknown Source:4)
at org.koin.core.instance.InstanceFactory.create(InstanceFactory.kt:50)
... 72 more
AddToFavourites Usecase:
class AddToFavourites(private val coreRepository: IPokemonCoreRepository) {
suspend fun invokeAddToFavourites(pokemon: Pokemon) {
coreRepository.addPokemonToFavourites(pokemon)
}
}
Repository interface and model in domain layer
interface IPokemonCoreRepository {
suspend fun addPokemonToFavourites(pokemon: Pokemon)
suspend fun getFavouritePokemon() : List<Pokemon>
suspend fun removePokemonFromFavourite(pokemon: Pokemon)
}
data class Pokemon(
val id : Int,
val name : String,
val description : String,
val imageUrl : String
)
Repository Implementation and datasourse in data layer:
class PokemonCoreRepository(private val dataSource: PokemonCoreDataSource) : IPokemonCoreRepository {
override suspend fun addPokemonToFavourites(pokemon: Pokemon) {
return dataSource.addPokemonToFavourites(pokemon)
}
override suspend fun getFavouritePokemon(): List<Pokemon> {
return dataSource.getFavouritePokemon()
}
override suspend fun removePokemonFromFavourite(pokemon: Pokemon) {
return dataSource.removePokemonFromFavourite(pokemon)
}
}
interface PokemonCoreDataSource {
suspend fun addPokemonToFavourites(pokemon: Pokemon)
suspend fun getFavouritePokemon() : List<Pokemon>
suspend fun removePokemonFromFavourite(pokemon: Pokemon)
}
Datasourse implementaation in framework layer:
class PokemonCoreDataSourceImpl(private val database : PokemonDatabase) : PokemonCoreDataSource{
override suspend fun addPokemonToFavourites(pokemon: Pokemon) {
return database.pfDao.addToFavourite(PokemonFavouriteEntity(id = pokemon.id, name = pokemon.name,
description = pokemon.description, imageUrl = pokemon.imageUrl))
}
override suspend fun getFavouritePokemon(): List<Pokemon> {
return database.pfDao.getFavouritePokemon().map {
Pokemon(id = it.id, name = it.name, description = it.description, imageUrl = it.imageUrl)
}
}
override suspend fun removePokemonFromFavourite(pokemon: Pokemon) {
return database.pfDao.removeFromFavourite(PokemonFavouriteEntity(
id = pokemon.id, name = pokemon.name,
description = pokemon.description, imageUrl = pokemon.imageUrl
))
}
}
Koin di Modules :
val useCaseModule = module {
factory { AddToFavourites(get()) }
factory { GetFavourites(get()) }
factory { RemoveFromFavourite(get()) }
}
val repositoryModule = module {
single<IPokemonCoreRepository> { PokemonCoreRepository(get()) }
}
val dataSourceModule = module {
single<PokemonCoreDataSource> { PokemonCoreDataSourceImpl(get()) }
}
val databaseModule = module {
single { PokemonDatabaseFactory.getDBInstance(get()) }
}
val viewModelModule = module {
viewModel { SearchViewModel(get(), get(), get()) }
}
Database and entity in framework layer:
#Database(entities = [PokemonFavouriteEntity::class], version = 1, exportSchema = false)
abstract class PokemonDatabase : RoomDatabase() {
abstract val pfDao : PokemonFavouriteDao
}
object PokemonDatabaseFactory {
fun getDBInstance(context: Context) {
Room.databaseBuilder(context, PokemonDatabase::class.java, "PokemonDB")
.fallbackToDestructiveMigration()
.build()
}
}
#Dao
interface PokemonFavouriteDao {
#Insert(onConflict = REPLACE)
suspend fun addToFavourite(pokemon : PokemonFavouriteEntity)
}
#Entity(tableName = "favourites")
data class PokemonFavouriteEntity(
#PrimaryKey(autoGenerate = true) val id : Int,
#ColumnInfo(name = "pokemonName") val name : String = "",
#ColumnInfo(name = "pokemonDescription") val description : String = "",
#ColumnInfo(name = "pokemonImage")val imageUrl : String = ""
)
Viewmodel in the presentation layer:
class SearchViewModel (private val getPokemonDescription: GetPokemonDescription,
private val getPokemonSprites: GetPokemonSprites,
private val addToFavourites: AddToFavourites) : ViewModel() {
private val _pokemon = MutableLiveData<Resource<PokemonResponse>>()
private val _pokemonSprites = MutableLiveData<Resource<List<String>>>()
private val _pokemonSpecies = MutableLiveData<Resource<PokemonSpeciesResponse>>()
val pokemon: LiveData<Resource<PokemonResponse>> get() = _pokemon
val pokemonSpecies: LiveData<Resource<PokemonSpeciesResponse>> get() = _pokemonSpecies
fun getPokemonDetails(pokemonName: String) {
_pokemon.value = Resource.Loading()
getPokemonSprites.execute(PokemonSubscriber(), pokemonName)
}
fun getPokemonSpeciesDetails(pokemonName: String) {
_pokemonSpecies.value = Resource.Loading()
getPokemonDescription.execute(PokemonSpeciesSubscriber(), pokemonName)
}
fun addToFavourite() {
viewModelScope.launch {
withContext(Dispatchers.IO) {
addToFavourites.invokeAddToFavourites(getPokemonDetails())
}
}
}
inner class PokemonSubscriber : DisposableSingleObserver<PokemonResponse>() {
override fun onSuccess(pokemonResponse: PokemonResponse) {
_pokemon.value = Resource.Success(pokemonResponse)
}
override fun onError(error: Throwable) {
_pokemon.value = Resource.Error(error.message)
}
}
inner class PokemonSpeciesSubscriber : DisposableSingleObserver<PokemonSpeciesResponse>() {
override fun onSuccess(pokemonSpeciesResponse: PokemonSpeciesResponse) {
_pokemonSpecies.value = Resource.Success(pokemonSpeciesResponse)
}
override fun onError(error: Throwable) {
_pokemonSpecies.value = Resource.Error(error.message)
}
}
private fun getPokemonDetails(): Pokemon {
var pokemonId = 0
var pokemonName = ""
var pokemonDescription = ""
var pokemonImage = ""
_pokemon.observeForever {
when (it) {
is Resource.Success -> {
pokemonImage = it.result.sprites.front_default
}
else -> {
}
}
}
_pokemonSpecies.observeForever {
when (it) {
is Resource.Success -> {
pokemonId = it.result.id
pokemonName = it.result.name
pokemonDescription = it.result.flavor_text_entries[0].flavor_text
}
else -> {
}
}
}
return Pokemon(pokemonId, pokemonName, pokemonDescription, pokemonImage)
}
override fun onCleared() {
super.onCleared()
getPokemonDescription.dispose()
getPokemonSprites.dispose()
}
}
You are not returning the created Database instance. Add return statement to the factory function or return using =
Like this:
object PokemonDatabaseFactory {
fun getDBInstance(context: Context) =
Room.databaseBuilder(context, PokemonDatabase::class.java, "PokemonDB")
.fallbackToDestructiveMigration()
.build()
}
Update
Actually, you don't need a factory function in Database. You can directly create an DB object from module through Koin, like this :
single {
Room.databaseBuilder(context, PokemonDatabase::class.java, "PokemonDB")
.fallbackToDestructiveMigration()
.build()
}