Kotlin null pointer exception when trying to get context from Registar in Flutter - kotlin

I'm trying to create a Flutter Plugin that uses a native Android sdk
I'm able to compile the .aar libraries of the sdk and use them in the project but the sdk requires me to get the context of the main activity
here is the error im getting
E/MethodChannel#mychart_plugin(16277): Failed to handle method call
E/MethodChannel#mychart_plugin(16277): kotlin.KotlinNullPointerException
E/MethodChannel#mychart_plugin(16277): at org.ccf.flutter.plugin.mychart_plugin.MychartPlugin.getContext(MychartPlugin.kt:79)
E/MethodChannel#mychart_plugin(16277): at epic.mychart.android.library.api.authentication.WPAPIAuthentication$1.getContext(WPAPIAuthentication.java:564)
E/MethodChannel#mychart_plugin(16277): at epic.mychart.android.library.prelogin.AuthenticationService.libraryLogin(AuthenticationService.java:461)
E/MethodChannel#mychart_plugin(16277): at epic.mychart.android.library.api.authentication.WPAPIAuthentication.login(WPAPIAuthentication.java:411)
E/MethodChannel#mychart_plugin(16277): at org.ccf.flutter.plugin.mychart_plugin.MychartPlugin.onMethodCall(MychartPlugin.kt:42)
E/MethodChannel#mychart_plugin(16277): at io.flutter.plugin.common.MethodChannel$IncomingMethodCallHandler.onMessage(MethodChannel.java:201)
E/MethodChannel#mychart_plugin(16277): at io.flutter.embedding.engine.dart.DartMessenger.handleMessageFromDart(DartMessenger.java:88)
E/MethodChannel#mychart_plugin(16277): at io.flutter.embedding.engine.FlutterJNI.handlePlatformMessage(FlutterJNI.java:219)
E/MethodChannel#mychart_plugin(16277): at android.os.MessageQueue.nativePollOnce(Native Method)
E/MethodChannel#mychart_plugin(16277): at android.os.MessageQueue.next(MessageQueue.java:325)
E/MethodChannel#mychart_plugin(16277): at android.os.Looper.loop(Looper.java:142)
E/MethodChannel#mychart_plugin(16277): at android.app.ActivityThread.main(ActivityThread.java:6541)
E/MethodChannel#mychart_plugin(16277): at java.lang.reflect.Method.invoke(Native Method)
E/MethodChannel#mychart_plugin(16277): at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
E/MethodChannel#mychart_plugin(16277): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767)
here is my code for getting the context
class MychartPlugin: MethodCallHandler, WPAPIAuthentication.IWPOnLoginListener {
private var registrar: PluginRegistry.Registrar? = null
private val LOGIN_REQUEST_CODE = 9876
fun MychartPlugin(registrar: PluginRegistry.Registrar) {
this.registrar = registrar
}
companion object {
#JvmStatic
fun registerWith(registrar: Registrar) {
val channel = MethodChannel(registrar.messenger(), "mychart_plugin")
channel.setMethodCallHandler(MychartPlugin())
}
}
override fun onMethodCall(call: MethodCall, result: Result) {
if (call.method == "getPlatformVersion") {
result.success("Android ${android.os.Build.VERSION.RELEASE}")
} else if (call.method == "MyChartSdkLogin") {
WPAPIAuthentication.login(this, "TURKJ123", "TurkJ123", LOGIN_REQUEST_CODE)
result.success("called MyChartSdkLogin")
} else {
result.notImplemented()
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {
// super.onActivityResult(requestCode, resultCode, data)
if (requestCode == LOGIN_REQUEST_CODE) {
if (resultCode == Activity.RESULT_OK) {
// login successful
Log.i("LoginFragment", "OK")
} else {
// login failed
val result = WPAPIAuthentication.getLoginResult(data)
Log.i("LoginFragment", result.toString())
val errorMessage = result.getErrorMessage(getContext())
if (!errorMessage.isEmpty()) {
Toast.makeText(getContext(), errorMessage, Toast.LENGTH_LONG).show()
}
}
}
}
override fun startActivityForResult(p0: Intent, p1: Int) {
// super.startActivityForResult(p0, p1)
}
override fun getSupportFragmentManager(): FragmentManager {
val act = registrar!!.activity() as FragmentActivity
return act.getSupportFragmentManager()
}
override fun getContext(): Context {
val cxt = registrar!!.context()
return cxt
}
}
notice the override getContext() method that im overriding from the sdk, I think this context call to the registar is where my KotlinNullPointerException is coming from

In getContext you have registrar!!. And registrar is a nullable field which you initialize to null and can only set in a method which you never call. Note that fun MychartPlugin isn't a constructor, you'd call it as e.g.
val plugin = MychartPlugin()
plugin.MychartPlugin(registrar)
But it doesn't seem like there is any reason to make registrar nullable or mutable in the first place. You can change to
class MychartPlugin(private val registrar: Registrar): ...
and
fun registerWith(registrar: Registrar) {
val channel = MethodChannel(registrar.messenger(), "mychart_plugin")
channel.setMethodCallHandler(MychartPlugin(registrar))
}

Related

Kotlin: I have a problem while switching from one Activity to another?

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()
}
}

Application Crashes Accepting Photo In Landscape Mode

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)
}
}

Weird behaviour with kotlin/kotlin-coroutines and list of result pairs

I'm working with kotlin/kotlin-coroutines and facing a weird behaviour with a List of kotlin.Result. Here is my code
snippet with example values to represent my issue:
val input = listOf("input_1", "input_2", "input_3")
// List<Pair<String, Result<ByteArray>>>
val asyncResults = input.map {
async { it to
runCatching { asyncCall(it) }
}
}.awaitAll()
// name: String, result: Result<ByteArray>
asyncResults.forEach { (name, result) ->
result.onSuccess {
println(it)
}
}
suspend fun asyncCall(str: String): ByteArray {
// operate with string and return ByteArray
}
When I run my application I'm retrieving the following Exception:
Exception in thread "main" java.lang.ClassCastException: class kotlin.Result cannot be cast to class [B (kotlin.Result is in unnamed module of loader 'app'; [B is in module java.base of loader 'bootstrap')
at MainBlockingKt$main$1.invokeSuspend(MainBlocking.kt:22)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at kotlinx.coroutines.internal.LimitedDispatcher.run(LimitedDispatcher.kt:39)
at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:95)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:750)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
Why can I not use onSuccess or onFailure with the result variable? When I'm just printing the result it shows the
following content:
// name: String, result: Result<ByteArray>
asyncResults.forEach { (name, result) ->
println(result)
}
Success(Success([B#7ea37dbf))
Success(Success([B#4b44655e))
Success(Failure(java.lang.IllegalArgumentException: <exception text>))
My result variable seems to be Result<Result<ByteArray>> instead of Result<ByteArray>>?? I'm really confused and didn't
find any solution on SO or on the web...
I tried to reproduce your error. I believe I made a complete kotlin program with identical code. The following code works without any error, so I'm not sure what's wrong but maybe with this full code you can find your error:
import kotlinx.coroutines.*
suspend fun main() {
GlobalScope.launch {
val input = listOf("input_1", "input_2", "input_3")
// List<Pair<String, Result<ByteArray>>>
val asyncResults = input.map {
async { it to
runCatching { asyncCall(it) }
}
}.awaitAll()
// name: String, result: Result<ByteArray>
asyncResults.forEach { (name, result) ->
result.onSuccess {
println(it)
}
}
}
delay(1000)
}
suspend fun asyncCall(str: String): ByteArray {
delay(100)
return ByteArray(1)
}

org.koin.core.error.InstanceCreationException: Could not create instance for [Factory:'com.pokemonsearch.presentation.search.SearchViewModel']

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()
}

ClassCastException in parcel

When I try to use the ArrayList in Second_Activity, I get an error.
I can't find a solution.
I've try:
data class Site(
var site: String? = null,
var imageArray: ArrayList<Uri>? = null,
var date: String? = null
) : Parcelable {
constructor(parcel: Parcel) : this(parcel.readString(),
parcel.readArrayList(ClassLoader.getSystemClassLoader()) as ArrayList<Uri>?,
parcel.readString())
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(site)
parcel.writeArray(arrayOf(imageArray))
parcel.writeString(date)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<Site> {
override fun createFromParcel(parcel: Parcel): Site {
return Site(parcel)
}
override fun newArray(size: Int): Array<Site?> {
return arrayOfNulls(size)
}
}
}
var mArray = ArrayList<Site>()
I try to use for 'mArray[0].date'
This is the error I get:
java.lang.RuntimeException: Unable to start activity ComponentInfo{kr.ac.***.Second_Activity}:
java.lang.ClassCastException: kr.ac.***.First_Activity$Site cannot be cast to kr.ac.***.Second_Activity$Site
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2950)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3085)
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:1816)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:6853)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:860)
I'm just not exactly sure how to read / write the ArrayList when parceling.
How can I use ArrayList in Second_Activity?
Thanks.