I have an activity which loads a list usign paging 3.
I use a channel to emit search text and then consume it as flow to apply transformations to get a flow of List of items
when the app under goes a configuration change I get the following error
ReceiveChannel.consumeAsFlow can be collected just once
Here is my psuedo code
fun onCreate() {
lifecycleScope.launch {
viewModel.skuList.collectLatest {
adapter.submitData(it)
}
}
}
In viewModel
val skuList = _searchChannel.consumeAsFlow().flatMapLatest {
skuRepository.fetchSku(it.activeOrderId, it.supplierId,
it.name, it.fetchDiscounted, it.skuCategoryIdCombinedText)
}
}
Fatal Exception: java.lang.IllegalStateException: ReceiveChannel.consumeAsFlow can be collected just once
at kotlinx.coroutines.flow.ChannelAsFlow.markConsumed(Channels.kt:130)
at kotlinx.coroutines.flow.ChannelAsFlow.collect(Channels.kt:153)
at kotlinx.coroutines.flow.internal.ChannelFlowTransformLatest$flowCollect$3.invokeSuspend(Merge.kt:101)
at kotlinx.coroutines.flow.internal.ChannelFlowTransformLatest$flowCollect$3.invoke(:8)
at kotlinx.coroutines.flow.internal.ChannelFlowTransformLatest$flowCollect$3.invoke(:4)
at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:89)
at kotlinx.coroutines.flow.internal.FlowCoroutineKt.flowScope(FlowCoroutine.kt:33)
at kotlinx.coroutines.flow.internal.ChannelFlowTransformLatest.flowCollect(Merge.kt:25)
at kotlinx.coroutines.flow.internal.ChannelFlowOperator.collectTo$suspendImpl(ChannelFlow.kt:157)
at kotlinx.coroutines.flow.internal.ChannelFlowOperator.collectTo()
at kotlinx.coroutines.flow.internal.ChannelFlow$collectToFun$1.invokeSuspend(ChannelFlow.kt:60)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at kotlinx.coroutines.EventLoop.processUnconfinedEvent(EventLoop.common.kt:69)
at kotlinx.coroutines.internal.DispatchedContinuationKt.resumeCancellableWith(DispatchedContinuation.kt:375)
at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:30)
at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable$default(Cancellable.kt:25)
at kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.kt:110)
at kotlinx.coroutines.AbstractCoroutine.start(AbstractCoroutine.kt:126)
at kotlinx.coroutines.BuildersKt__Builders_commonKt.launch(Builders.common.kt:56)
at kotlinx.coroutines.BuildersKt.launch(:1)
at kotlinx.coroutines.BuildersKt__Builders_commonKt.launch$default(Builders.common.kt:47)
at kotlinx.coroutines.BuildersKt.launch$default(:1)
at com.awantunai.app.home.cart.ordering.add_to_cart.AddToCartV2Activity.setupRecyclerView(AddToCartV2Activity.kt:272)
at com.awantunai.app.home.cart.ordering.add_to_cart.AddToCartV2Activity.initViews(AddToCartV2Activity.kt:207)
at com.awantunai.app.home.cart.ordering.add_to_cart.AddToCartV2Activity.onCreate(AddToCartV2Activity.kt:105)
at android.app.Activity.performCreate(Activity.java:8054)
at android.app.Activity.performCreate(Activity.java:8034)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1341)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3688)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3864)
at android.app.ActivityThread.handleRelaunchActivityInner(ActivityThread.java:5811)
at android.app.ActivityThread.handleRelaunchActivity(ActivityThread.java:5703)
at android.app.servertransaction.ActivityRelaunchItem.execute(ActivityRelaunchItem.java:71)
at android.app.servertransaction.ActivityTransactionItem.execute(ActivityTransactionItem.java:45)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2253)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loopOnce(Looper.java:201)
at android.os.Looper.loop(Looper.java:288)
at android.app.ActivityThread.main(ActivityThread.java:7870)
at java.lang.reflect.Method.invoke(Method.java)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1003)
You should be using repeatOnLifeCycle in order to keep collecting/emiting to your UI after a configuration change
// Create a new coroutine since repeatOnLifecycle is a suspend function
lifecycleScope.launch {
// The block passed to repeatOnLifecycle is executed when the lifecycle
// is at least STARTED and is cancelled when the lifecycle is STOPPED.
// It automatically restarts the block when the lifecycle is STARTED again.
repeatOnLifecycle(Lifecycle.State.STARTED) {
// Safely collect from skuList when the lifecycle is STARTED
// and stops collection when the lifecycle is STOPPED
lviewModel.skuList.collectLatest {
// New skuList
}
}
}
You can also take a look at flowWithLifecycle
As a workaround used receiveAsFlow instead of consumeAsFlow
Related
The scenario is when the app is on Fragment A and then kept the app in the background for some time. After resuming from the background the initial state of the app is in Fragment A but then suddenly app restarts from startDestination in the navigation component causing the below crash.
Fatal Exception: java.lang.IllegalArgumentException: Navigation action/destination com.app.gulfcraftv2:id/action_splashFragment_to_nav_home cannot be found from the current destination Destination(com.app.gulfcraftv2:id/companyFragment) class=com.precise.gulfcraft.ui.companydetail.CompanyDetailFragment
at androidx.navigation.NavController.navigate(NavController.kt:1540)
at androidx.navigation.NavController.navigate(NavController.kt:1472)
at androidx.navigation.NavController.navigate(NavController.kt:1930)
at com.precise.gulfcraft.ui.splash.SplashFragment.onCreate$lambda-6(SplashFragment.kt:75)
at com.precise.gulfcraft.ui.splash.SplashFragment.lambda$Aso8YI02DbFhfWptH-qrBUU0ulU()
at com.precise.gulfcraft.ui.splash.-$$Lambda$SplashFragment$Aso8YI02DbFhfWptH-qrBUU0ulU.run(:2)
at android.os.Handler.handleCallback(Handler.java:883)
at android.os.Handler.dispatchMessage(Handler.java:100)
at android.os.Looper.loop(Looper.java:224)
at android.app.ActivityThread.main(ActivityThread.java:7562)
at java.lang.reflect.Method.invoke(Method.java)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:539)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:950)
Note - The launch mode used is singleTop
The crash is happening in the below section.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
try {
Handler(Looper.getMainLooper()).postDelayed({
findNavController().navigate(SplashFragmentDirections.actionSplashFragmentToNavHome())
}, 3000)
} catch (e: Exception) {
}
}
check first if this is your current destination with following function
fun navigate(destination: NavDirections) = with(findNavController()) {
currentDestination?.getAction(destination.actionId)
?.let { navigate(destination) }
}
I have a MainActivity using a DrawerLayout and tabs with 2 fragments.
My first fragment contains a list of elements in a RecyclerView, and I can click on each element to "select" it (which calls a SDK function to login to a hardware device). When selected, this triggers a change on the Fragment's ViewModel:
// Selected device changes when an item is clicked
private val _devices = MutableLiveData<List<DeviceListItemViewModel>>()
private val _selectedDevice = MutableLiveData<ConnectedDevice>()
val devices: LiveData<List<DeviceListItemViewModel>> by this::_devices
val selectedDevice: LiveData<ConnectedDevice> by this::_selectedDevice
Then I have a shared ViewModel between both fragments, which also has a currentDevice variable like this:
private val _currentDevice = MutableLiveData<ConnectedDevice>()
val currentDevice: LiveData<ConnectedDevice> by this::_currentDevice
So in the Fragment that contains the list, I have the following code to update the shared ViewModel variable:
private val mViewModel: DeviceManagementViewModel by viewModels()
private val mSharedViewModel: MainActivityViewModel by activityViewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
Log.d(classTag, "Fragment view created")
val binding = ActivityMainDevicesManagementFragmentBinding.bind(view)
binding.apply {
viewModel = mViewModel
lifecycleOwner = viewLifecycleOwner
}
// Observe fragment ViewModel
// If any device is clicked on the list, do the login on the shared ViewModel
mViewModel.selectedDevice.observe(this, {
mSharedViewModel.viewModelScope.launch {
if (it != null) {
mSharedViewModel.setCurrentDevice(videoDevice = it)
} else mSharedViewModel.unsetCurrentDevice()
}
})
}
The problem is that if the shared ViewModel's currentDevice variable is set, I get exceptions whenever I try to open a Dialog or start a new activity. If I modify the setCurrentDevice function in the shared ViewModel, then it works fine (or if I don't select any device).
The exceptions I see are this when starting a new Activity:
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.placeholder.easyview/com.example.myapp.activities.settings.SettingsActivity}: java.lang.IllegalArgumentException: display must not be null
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3430)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3594)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:85)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2067)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:223)
at android.app.ActivityThread.main(ActivityThread.java:7698)
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:952)
Caused by: java.lang.IllegalArgumentException: display must not be null
at android.app.ContextImpl.createDisplayContext(ContextImpl.java:2386)
at android.content.ContextWrapper.createDisplayContext(ContextWrapper.java:977)
at com.android.internal.policy.DecorContext.<init>(DecorContext.java:50)
at com.android.internal.policy.PhoneWindow.generateDecor(PhoneWindow.java:2348)
at com.android.internal.policy.PhoneWindow.installDecor(PhoneWindow.java:2683)
at com.android.internal.policy.PhoneWindow.getDecorView(PhoneWindow.java:2116)
at androidx.appcompat.app.AppCompatActivity.initViewTreeOwners(AppCompatActivity.java:219)
at androidx.appcompat.app.AppCompatActivity.setContentView(AppCompatActivity.java:194)
at com.example.myapp.activities.settings.SettingsActivity.onCreate(SettingsActivity.kt:34)
at android.app.Activity.performCreate(Activity.java:8000)
at android.app.Activity.performCreate(Activity.java:7984)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1310)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3403)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3594)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:85)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2067)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:223)
at android.app.ActivityThread.main(ActivityThread.java:7698)
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:952)
And this if I try to open a Dialog:
java.lang.ArrayIndexOutOfBoundsException: length=16; index=2448
at android.view.InsetsState.peekSource(InsetsState.java:374)
at android.view.InsetsSourceConsumer.updateSource(InsetsSourceConsumer.java:291)
at android.view.InsetsController.updateState(InsetsController.java:654)
at android.view.InsetsController.onStateChanged(InsetsController.java:621)
at android.view.ViewRootImpl.setView(ViewRootImpl.java:1058)
at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:409)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:110)
at android.app.Dialog.show(Dialog.java:340)
at android.app.AlertDialog$Builder.show(AlertDialog.java:1131)
at com.example.myapp.activities.main.fragments.DeviceManagementFragment.showAddDeviceMethodDialog(DeviceManagementFragment.kt:151)
at com.example.myapp.activities.main.fragments.DeviceManagementFragment.access$showAddDeviceMethodDialog(DeviceManagementFragment.kt:33)
at com.example.myapp.activities.main.fragments.DeviceManagementFragment$onViewCreated$$inlined$apply$lambda$1.onClick(DeviceManagementFragment.kt:52)
at android.view.View.performClick(View.java:7448)
at android.view.View.performClickInternal(View.java:7425)
at android.view.View.access$3600(View.java:810)
at android.view.View$PerformClick.run(View.java:28309)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:223)
at android.app.ActivityThread.main(ActivityThread.java:7698)
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:952)
EDIT: Looks like the problem actually lies in the other fragment, where I have the following code (in onViewCreated method):
// If the shared view model device changes, this must change too
mSharedViewModel.currentDevice.observe(this, {
if (it != null) {
mViewModel.setCurrentDevice(it)
} else mViewModel.unsetCurrentDevice()
})
mViewModel.currentDevice.observe(this, {
if (it != null) {
mViewModel.fetchChannels()
}
})
If I comment out the second part (where the fetchChannels occurs), it works well. Even if I comment out the fetchChannels call only, it works.
This is the code of the fetchChannels function:
fun fetchChannels() = viewModelScope.launch {
Log.d(classTag, "Getting channels for device ${currentDevice.value}")
currentDevice.value?.let {
val fetchedChannels = deviceLibManager.getChannels(it.videoDevice)
_currentDevice.value?.channels?.clear()
_currentDevice.value?.channels?.addAll(fetchedChannels)
if (fetchedChannels.isNotEmpty()) {
_currentDevice.value?.currentChannel = fetchedChannels[0]
}
}
}
The following line is the one giving me trouble:
val fetchedChannels = deviceLibManager.getChannels(it.videoDevice)
That function is just this:
suspend fun getChannels(videoDevice: VideoDevice): List<VideoChannel> {
try {
Log.i(classTag, "Getting channels from device ${videoDevice}")
val channels = videoDevice.getChannelsAsync()
return channels
} catch (exception: Exception) {
when (exception) {
is UnknownVendorException -> {
Log.w(classTag, "Device ${videoDevice} cannot get channels because the vendor is unknown")
}
is NetworkException -> {
Log.w(classTag, "Device ${videoDevice} cannot get channels because it is unreachable")
}
else -> {
Log.w(classTag, "Device ${videoDevice} cannot get channels, reason: ${exception.message}")
}
}
return emptyList()
}
}
And the implementation in the SDK is this:
override suspend fun getChannelsAsync(): List<VideoChannel> = withContext(Dispatchers.IO) {
Log.i(classTag, "Trying to get channels for device: $logName")
val channels = ArrayList<VideoChannel>()
getZeroChannel()?.let {
channels.add(it)
}
channels.addAll(getAllChannels())
if (channels.isNotEmpty()) {
Log.i(classTag, "Successfully retrieved ${channels.size} channels for device: $logName")
return#withContext channels
} else {
Log.w(classTag, "Error retrieving channels for device $logName or no channels exist")
throw Exception()
}
}
The other functions just make a network call and retrieve some data, it should not be messing with the UI at all.
I am testing with a Xiaomi Mi A3 using Android 10.
Can someone help me? Thank you.
So I don't really know why but I found the answer.
In the SDK, the functions getZeroChannel and getAllChannels were not suspending functions, although they make a network call. So what I did is:
Move the withContext(Dispatchers.IO) part to those two functions (the ones who actually make the network call), and make them suspend functions.
Remove the withContext(Dispatchers.IO) part from getChannelsAsync function. Keep it as a suspend function though.
After these changes everything works as expected. I still don't know why, so if someone could shed some light, that would be very much appreciated.
I'm implementing paging from network + database cache using the new Paging 3 library. My implementation is similar to Android Paging codelab sample. The most significant difference is that I want to show DB data immediately while network data is loading. I've implemented a RemoteMediator and use a PagingSource provided by Room database.
#Dao
interface FooDao {
...
#Query("SELECT * FROM foo")
fun getFooSource(): PagingSource<Int, Foo>
}
By default no data is displayed in the RecycleView if there's no network connection. To display DB data from PagingSource immediately my RemoteMediator uses InitializeAction.SKIP_INITIAL_REFRESH and it solves the problem.
class MyRemoteMediator(...) : RemoteMediator<Int, Foo>() {
...
override suspend fun initialize(): InitializeAction {
return InitializeAction.SKIP_INITIAL_REFRESH
}
}
Now DB data is displayed immediately, but network data isn't loaded so I use PagingDataAdapter.refresh() method to trigger the request. I don't sure where is the proper place for the refresh() call, it doesn't work if I call it inside Fragment.onCreate() for example, so lets delay the call. I don't think it's a right way, just for an example:
class MyFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
lifecycleScope.launch {
delay(1000)
adapter.refresh()
}
}
}
Now it works almost as expected, DB data is displayed immediately and RemoteMediator loads data from the network and updates the database. The only problem, if there's no internet access RemoteMediator request fails and returns MediatorResult.Error. If I retry the request later using PagingDataAdapter.retry() method I get the following error when I scroll the list:
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.icesmith.tvprogramepg, PID: 6381
java.lang.IllegalStateException: Cannot coerce hint when no pages have loaded
at androidx.paging.PagerState.withCoercedHint$paging_common(PagerState.kt:313)
at androidx.paging.PageFetcherSnapshot.doLoad(PageFetcherSnapshot.kt:369)
at androidx.paging.PageFetcherSnapshot$startConsumingHints$2$invokeSuspend$$inlined$collect$1.emit(Collect.kt:137)
at kotlinx.coroutines.flow.FlowKt__ChannelsKt.emitAllImpl$FlowKt__ChannelsKt(Channels.kt:59)
at kotlinx.coroutines.flow.FlowKt__ChannelsKt$emitAllImpl$1.invokeSuspend(Unknown Source:11)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56)
at kotlinx.coroutines.EventLoop.processUnconfinedEvent(EventLoop.common.kt:69)
at kotlinx.coroutines.DispatchedTaskKt.resumeUnconfined(DispatchedTask.kt:184)
at kotlinx.coroutines.DispatchedTaskKt.dispatch(DispatchedTask.kt:108)
at kotlinx.coroutines.CancellableContinuationImpl.dispatchResume(CancellableContinuationImpl.kt:306)
at kotlinx.coroutines.CancellableContinuationImpl.completeResume(CancellableContinuationImpl.kt:393)
at kotlinx.coroutines.channels.AbstractChannel$ReceiveElement.completeResumeReceive(AbstractChannel.kt:888)
at kotlinx.coroutines.channels.ConflatedChannel.offerInternal(ConflatedChannel.kt:62)
at kotlinx.coroutines.channels.ConflatedBroadcastChannel$Subscriber.offerInternal(ConflatedBroadcastChannel.kt:295)
at kotlinx.coroutines.channels.ConflatedBroadcastChannel.offerInternal(ConflatedBroadcastChannel.kt:257)
at kotlinx.coroutines.channels.ConflatedBroadcastChannel.offer(ConflatedBroadcastChannel.kt:238)
at androidx.paging.PageFetcherSnapshot.addHint(PageFetcherSnapshot.kt:179)
at androidx.paging.PageFetcher$PagerUiReceiver.addHint(PageFetcher.kt:108)
at androidx.paging.PagingDataDiffer.get(PagingDataDiffer.kt:113)
at androidx.paging.AsyncPagingDataDiffer.getItem(AsyncPagingDataDiffer.kt:241)
at androidx.paging.PagingDataAdapter.getItem(PagingDataAdapter.kt:150)
at com.icesmith.tvprogramepg.fragment.channellist.ChannelAdapter.onBindViewHolder(ChannelAdapter.kt:31)
at com.icesmith.tvprogramepg.fragment.channellist.ChannelAdapter.onBindViewHolder(ChannelAdapter.kt:13)
at androidx.recyclerview.widget.RecyclerView$Adapter.onBindViewHolder(RecyclerView.java:7163)
at androidx.recyclerview.widget.RecyclerView$Adapter.bindViewHolder(RecyclerView.java:7243)
at androidx.recyclerview.widget.RecyclerView$Recycler.tryBindViewHolderByDeadline(RecyclerView.java:6110)
at androidx.recyclerview.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:6376)
at androidx.recyclerview.widget.GapWorker.prefetchPositionWithDeadline(GapWorker.java:288)
at androidx.recyclerview.widget.GapWorker.flushTaskWithDeadline(GapWorker.java:345)
at androidx.recyclerview.widget.GapWorker.flushTasksWithDeadline(GapWorker.java:361)
at androidx.recyclerview.widget.GapWorker.prefetch(GapWorker.java:368)
at androidx.recyclerview.widget.GapWorker.run(GapWorker.java:399)
at android.os.Handler.handleCallback(Handler.java:883)
at android.os.Handler.dispatchMessage(Handler.java:100)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7356)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
SO I am trying to observe some LiveData in my repository, so I can work with this data and update it's items according to my operations. But I can't figure out how to access the data. When I try to observe it, the ~Observeris asking for a LifecycleOwner. Reading about similar problems I've seen a suggestion to extendLifecycleService, but when I do I get the error sayingOnly one class may appear in a supertype list`.
How do I go around this?
This is my service:
class DetectJobIntentService : JobIntentService() {
private val TAG = "DetectJobIntentServi22"
fun enqueueWork(context: Context, work: Intent) {
enqueueWork(context, DetectJobIntentService::class.java, 12, work)
}
override fun onHandleWork(intent: Intent) {
Log.d(TAG, "onHandleWork")
val options = FirebaseVisionFaceDetectorOptions.Builder()
.setClassificationMode(FirebaseVisionFaceDetectorOptions.ACCURATE)
.setClassificationMode(FirebaseVisionFaceDetectorOptions.ALL_CLASSIFICATIONS)
.setMinFaceSize(0.15f)
.build()
val detector = FirebaseVision.getInstance()
.getVisionFaceDetector(options)
val repo = PhotoRepository(application)
val allPhotos = repo.getAllPhotos()
allPhotos.observe(SomeLifeCycleOwner, Observer {
for (file in it) {
val image = FirebaseVisionImage.fromFilePath(application, Uri.parse(file.uri))
detector.detectInImage(image).addOnSuccessListener { list ->
if (list.isNotEmpty()) {
file.hasFaces = 1
repo.update(file)
} else {
file.hasFaces = 2
repo.update(file)
}
}
}
})
}
}
EDIT: Following Sina's suggestion which sounds right, I've tried implementing a query that fetches the data into a non-LiveData object my app crashes.
This is the quesry I've added in my Dow:
#Query("SELECT * FROM photos_table")
fun getAllPhotosStatic(): MutableList<Photo>
If I run at at this point it's all good.
Then in my repository I've added this:
val allPhotosStatic = photoDao.getAllPhotosStatic()
And as soon as I've done that and try to run the app it crashes, and I get this stack:
2019-11-13 23:49:37.079 20720-20720/tech.levanter.anyvision E/AndroidRuntime: FATAL EXCEPTION: main
Process: tech.levanter.anyvision, PID: 20720
java.lang.RuntimeException: Unable to start activity ComponentInfo{tech.levanter.anyvision/tech.levanter.anyvision.MainActivity}: java.lang.RuntimeException: Cannot create an instance of class tech.levanter.anyvision.viewModels.AllPhotosViewModel
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2991)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3126)
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:1846)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:201)
at android.app.ActivityThread.main(ActivityThread.java:6882)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:547)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:873)
Caused by: java.lang.RuntimeException: Cannot create an instance of class tech.levanter.anyvision.viewModels.AllPhotosViewModel
at androidx.lifecycle.ViewModelProvider$AndroidViewModelFactory.create(ViewModelProvider.java:238)
at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:164)
at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:130)
at tech.levanter.anyvision.MainActivity.onCreate(MainActivity.kt:66)
at android.app.Activity.performCreate(Activity.java:7232)
at android.app.Activity.performCreate(Activity.java:7221)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1272)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2971)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3126)
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:1846)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:201)
at android.app.ActivityThread.main(ActivityThread.java:6882)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:547)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:873)
Caused by: java.lang.reflect.InvocationTargetException
at java.lang.reflect.Constructor.newInstance0(Native Method)
at java.lang.reflect.Constructor.newInstance(Constructor.java:343)
at androidx.lifecycle.ViewModelProvider$AndroidViewModelFactory.create(ViewModelProvider.java:230)
at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:164)
at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:130)
at tech.levanter.anyvision.MainActivity.onCreate(MainActivity.kt:66)
at android.app.Activity.performCreate(Activity.java:7232)
at android.app.Activity.performCreate(Activity.java:7221)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1272)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2971)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3126)
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:1846)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:201)
at android.app.ActivityThread.main(ActivityThread.java:6882)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:547)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:873)
Caused by: java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long period of time.
at androidx.room.RoomDatabase.assertNotMainThread(RoomDatabase.java:267)
at androidx.room.RoomDatabase.query(RoomDatabase.java:323)
at androidx.room.util.DBUtil.query(DBUtil.java:83)
at tech.levanter.anyvision.room.PhotoDao_Impl.getAllPhotosStatic(PhotoDao_Impl.java:154)
at tech.levanter.anyvision.room.PhotoRepository.<init>(PhotoRepository.kt:37)
at tech.levanter.anyvision.viewModels.AllPhotosViewModel.<init>(AllPhotosViewModel.kt:12)
at java.lang.reflect.Constructor.newInstance0(Native Method)
at java.lang.reflect.Constructor.newInstance(Constructor.java:343)
at androidx.lifecycle.ViewModelProvider$AndroidViewModelFactory.create(ViewModelProvider.java:230)
at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:164)
at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:130)
at tech.levanter.anyvision.MainActivity.onCreate(MainActivity.kt:66)
at android.app.Activity.performCreate(Activity.java:7232)
at android.app.Activity.performCreate(Activity.java:7221)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1272)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2971)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3126)
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:1846)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:201)
at android.app.ActivityThread.main(ActivityThread.java:6882)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:547)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:873)
I thought maybe I can just get a copy of the data into a static list in my repository, but for that I'd need to observe it in the repository and I'm stuck at the same place again because I don't have a lifecycleOwner.
The idea in MVVM is to observe from view if you are doing an action that you want to be sure is finished do not wrap it in a livedata. Basically you don't need any life cycle aware component. Your query should return list of photos without any need to observe it. I mean in your dao if you don't wrap data in a livedata you don't need to observe anything and getAllPhotos return photos. You can do this kind of thing anywhrere just do everything from background, in a thread or something. For both kind of designs you can have 1 livedata wraped method in dao and 1 method without livedata for jobs that need to be done serially.
Update: put your code in a background thread:
new Thread(new Runnable() {
#Override
public void run() {
val allPhotosStatic = photoDao.getAllPhotosStatic()
for (file in it) {
val image = FirebaseVisionImage.fromFilePath(application, Uri.parse(file.uri))
detector.detectInImage(image).addOnSuccessListener { list ->
if (list.isNotEmpty()) {
file.hasFaces = 1
repo.update(file)
} else {
file.hasFaces = 2
repo.update(file)
}
}
}
}
}).start();
or:
AsyncTask.execute(new Runnable() {
#Override
public void run() {
val allPhotosStatic = photoDao.getAllPhotosStatic()
for (file in it) {
val image = FirebaseVisionImage.fromFilePath(application, Uri.parse(file.uri))
detector.detectInImage(image).addOnSuccessListener { list ->
if (list.isNotEmpty()) {
file.hasFaces = 1
repo.update(file)
} else {
file.hasFaces = 2
repo.update(file)
}
}
}
}
});
I'm building an app using clean architecture.
I'm getting an error when calling an use case from my ViewModel that needs to run a query on my Room Database, telling me I'm accessing it in the main thread.
Caused by: java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long period of time.
at android.arch.persistence.room.RoomDatabase.assertNotMainThread(RoomDatabase.java:204)
at android.arch.persistence.room.RoomDatabase.query(RoomDatabase.java:232)
at com.something.data.db.PatientDAO_Impl.getAllPatients(PatientDAO_Impl.java:229)
at com.something.data.repositories.dataSource.PatientDataStoreFactory.create(PatientDataStoreFactory.kt:18)
at com.something.data.repositories.PatientRepositoryImplementation.getPatient(PatientRepositoryImplementation.kt:97)
at com.something.domain.useCases.GetPatientUseCase.execute(GetPatientUseCase.kt:10)
at com.something.some_thing.searchForm.SearchFormViewModel.getPatientFromDb(SearchFormViewModel.kt:49)
at com.something.some_thing.searchForm.SearchFormFragment.onCreateView(SearchFormFragment.kt:99)
at android.support.v4.app.Fragment.performCreateView(Fragment.java:2439)
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1460)
at android.support.v4.app.FragmentManagerImpl.moveFragmentToExpectedState(FragmentManager.java:1784)
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1852)
at android.support.v4.app.BackStackRecord.executeOps(BackStackRecord.java:802)
at android.support.v4.app.FragmentManagerImpl.executeOps(FragmentManager.java:2625)
at android.support.v4.app.FragmentManagerImpl.executeOpsTogether(FragmentManager.java:2411)
at android.support.v4.app.FragmentManagerImpl.removeRedundantOperationsAndExecute(FragmentManager.java:2366)
at android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:2273)
at android.support.v4.app.FragmentManagerImpl.dispatchStateChange(FragmentManager.java:3273)
at android.support.v4.app.FragmentManagerImpl.dispatchActivityCreated(FragmentManager.java:3229)
at android.support.v4.app.FragmentController.dispatchActivityCreated(FragmentController.java:201)
at android.support.v4.app.FragmentActivity.onStart(FragmentActivity.java:620)
at android.support.v7.app.AppCompatActivity.onStart(AppCompatActivity.java:178)
at android.app.Instrumentation.callActivityOnStart(Instrumentation.java:1334)
at android.app.Activity.performStart(Activity.java:7057)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2819)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2931)
at android.app.ActivityThread.-wrap11(Unknown Source:0)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1620)
at android.os.Handler.dispatchMessage(Handler.java:105)
at android.os.Looper.loop(Looper.java:173)
at android.app.ActivityThread.main(ActivityThread.java:6698)
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:782)
Here is some code
My ViewModel call:
fun getPatient(){
val disposable = getPatientUseCase.execute()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::getPatientSuccess, this::onError)
compositeDisposable.add(disposable)
}
My Use Case:
class GetPatientUseCase(private val patientRepository: PatientRepository){
fun execute(): Single<Patient> {
return patientRepository.getPatient()
}
My repository method implementation
override fun getPatient(): Single<Patient> {
val patientDataStore = PatientDataStoreFactory().create()
return patientDataStore.patient()
}
This is where the crash happens, in My PatientDataStoreFactory, when running db.patientDAO().getAllPatients().isNotEmpty():
#Singleton
class PatientDataStoreFactory {
private val db = DaggerWrapper.getInstance().database()
fun create(): PatientDataStore {
return if (db.patientDAO().getAllPatients().isNotEmpty()){
LocalPatientDataStore()
} else {
CloudPatientDataStore()
}
}
}
In my ViewModel I'm subscribing the use case execution so I dont understand this crash...
Any ideas?
The problem is here: db.patientDAO().getAllPatients(). You are calling getAllPatients() on the UI Thread. You should wrap in in the Single and subscribe on Schedulers.io().
One of the possible solutions is to rewrite the following code:
val patientDataStore = PatientDataStoreFactory().create()
return patientDataStore.patient()
to something like this:
return Single.fromCallable { PatientDataStoreFactory().create() }
.flatMap { store -> store.patient() }