androidbroadcast / ViewBindingPropertyDelegate

Make work with Android View Binding simpler
https://proandroiddev.com/make-android-view-binding-great-with-kotlin-b71dd9c87719
Apache License 2.0
1.42k stars 102 forks source link

Host view isn't ready to create a ViewBinding instance #93

Closed DatL4g closed 2 years ago

DatL4g commented 2 years ago

I'm facing an issue with the latest version (1.5.3) where Host view isn't ready to create a ViewBinding instance occurs.

Full Stacktrace:

java.lang.IllegalStateException: Host view isn't ready to create a ViewBinding instance
        at by.kirich1409.viewbindingdelegate.LifecycleViewBindingProperty.getValue(ViewBindingProperty.kt:87)
        at by.kirich1409.viewbindingdelegate.FragmentViewBindingProperty.getValue(FragmentViewBindings.kt:61)
        at by.kirich1409.viewbindingdelegate.FragmentViewBindingProperty.getValue(FragmentViewBindings.kt:51)
        at de.datlag.burningseries.ui.fragment.ScrapeHosterFragment.getBinding(ScrapeHosterFragment.kt:34)
        at de.datlag.burningseries.ui.fragment.ScrapeHosterFragment.access$getBinding(ScrapeHosterFragment.kt:29)
        at de.datlag.burningseries.ui.fragment.ScrapeHosterFragment$saveStream$1$1.invokeSuspend(ScrapeHosterFragment.kt:92)
        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:938)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:233)
        at android.app.ActivityThread.main(ActivityThread.java:8030)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:631)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:978)

I'm able to reproduce this consistently BurningSeries-Android inside the SeriesFragment. The normal way I navigate there is from HomeFragment and this works fine, however when I navigate to the VideoFragment from there and then to the ScrapeHosterFragment and after that to the SaveScrapedDialog lastly back to the SeriesFragment the app crashes because of this error.

It's maybe hard to understand but basicly the navigation hierarchy looks like this:

HomeFragment -> SeriesFragment = FINE

HomeFragment -> SeriesFragment -> VideoFragment -> StreamUnavailableDialog -> ScrapeHosterFragment -> SaveScrapedDialog -> SeriesFragment = ERROR

All navigations are done using the NavigationComponent with SafeArgs. I'm accessing the views in the onViewCreated method.

kirich1409 commented 2 years ago

You try to access in another thread (from Coroutine). I think View can be destroyed and that's why ViewBinding can't be created

DatL4g commented 2 years ago

Mhm no I don't think so. Or did I miss something? I navigate back there in the Main thread

I start the flow collection in onViewCreated and do it the official recommended way (This should collect the flow in main thread)

inline fun <T> Flow<T>.launchAndCollectIn(
    owner: LifecycleOwner,
    minActiveState: Lifecycle.State = Lifecycle.State.STARTED,
    crossinline action: suspend CoroutineScope.(T) -> Unit
) = owner.lifecycleScope.launch {
    owner.repeatOnLifecycle(minActiveState) {
        collect {
            action(it)
        }
    }
}

Code snippet

navArgs.seriesWithInfo?.let { series ->
    setViewData(series)
    burningSeriesViewModel.getSeriesData(series.series.href, series.series.hrefTitle).launchAndCollect {
        seriesFlowCollect(it)
    }
}

And the navigation from HomeFragment works (but uses another navArg)

kirich1409 commented 2 years ago

I am working on release with better error description that will help easier understand the reason

DatL4g commented 2 years ago

Nice. I look forward to it

kirich1409 commented 2 years ago

Version 1.5.4 must be available in few hours

DatL4g commented 2 years ago

I cannot resolve Version 1.5.4 although the README displays version 1.5.4 (badge) and https://mvnrepository.com/artifact/com.github.kirich1409/viewbindingpropertydelegate/1.5.4 exists. Can you check that?

I already tried Invalidate and Restart in Android Studio as well as Refresh Dependencies using the Gradle tab in Android Studio.

Gradle Sync output:

Failed to resolve: viewbindingpropertydelegate-1.5.4
Affected Modules: app

Gradle implementation:

implementation("com.github.kirich1409:viewbindingpropertydelegate:1.5.4")
kirich1409 commented 2 years ago

I've checked and has no any issues. Try to remove Gradle cache in $HOME/.gradle/caches and try again to build your project

DatL4g commented 2 years ago

Just deleted the folder, opened the project and downloaded everything. Still said it cannot resolve the dependency (1.5.3 however can), tried to build anyway

FAILURE: Build completed with 8 failures.

1: Task failed with an exception.
-----------
* What went wrong:
Execution failed for task ':app:dataBindingMergeDependencyArtifactsDebug'.
> Could not resolve all files for configuration ':app:debugCompileClasspath'.
   > Failed to transform viewbindingpropertydelegate-1.5.4.aar (com.github.kirich1409:viewbindingpropertydelegate:1.5.4) to match attributes {artifactType=android-databinding, org.gradle.category=library, org.gradle.dependency.bundling=external, org.gradle.libraryelements=aar, org.gradle.status=release, org.gradle.usage=java-api}.
      > Could not find viewbindingpropertydelegate-1.5.4.aar (com.github.kirich1409:viewbindingpropertydelegate:1.5.4).
        Searched in the following locations:
            https://repo.maven.apache.org/maven2/com/github/kirich1409/viewbindingpropertydelegate/1.5.4/viewbindingpropertydelegate-1.5.4.aar

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.
==============================================================================

Other 7 errors are the same but other task names

DatL4g commented 2 years ago

There is no aar file, only jars and checksums etc https://repo.maven.apache.org/maven2/com/github/kirich1409/viewbindingpropertydelegate/1.5.4/

kirich1409 commented 2 years ago

I've published 1.5.5 few minutes ago

DatL4g commented 2 years ago

Resolving 1.5.5 works however the is a duplicate class issue which prevents the app from building. (Building still works fine when I switch back to 1.5.3, already checked my dependency tree)

Execution failed for task ':app:checkDebugDuplicateClasses'.
> A failure occurred while executing com.android.build.gradle.internal.tasks.CheckDuplicatesRunnable
   > Duplicate class by.kirich1409.viewbindingdelegate.ViewBindingPropertyDelegateKt found in modules jetified-viewbindingpropertydelegate-1.5.5-runtime (com.github.kirich1409:viewbindingpropertydelegate:1.5.5) and jetified-viewbindingpropertydelegate-noreflection-1.5.5-runtime (com.github.kirich1409:viewbindingpropertydelegate-noreflection:1.5.5)
     Duplicate class by.kirich1409.viewbindingdelegate.ViewBindingPropertyDelegateKt$viewBindingWithLifecycle$1 found in modules jetified-viewbindingpropertydelegate-1.5.5-runtime (com.github.kirich1409:viewbindingpropertydelegate:1.5.5) and jetified-viewbindingpropertydelegate-noreflection-1.5.5-runtime (com.github.kirich1409:viewbindingpropertydelegate-noreflection:1.5.5)
     Duplicate class by.kirich1409.viewbindingdelegate.ViewBindingPropertyDelegateKt$viewBindingWithLifecycle$2 found in modules jetified-viewbindingpropertydelegate-1.5.5-runtime (com.github.kirich1409:viewbindingpropertydelegate:1.5.5) and jetified-viewbindingpropertydelegate-noreflection-1.5.5-runtime (com.github.kirich1409:viewbindingpropertydelegate-noreflection:1.5.5)

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.
DatL4g commented 2 years ago

Switched to the noreflection variant for now. This builds fine

DatL4g commented 2 years ago

Back to the original issue:

The log shows

2022-01-05 22:25:46.868 16285-4024/de.datlag.burningseries E/AndroidRuntime: FATAL EXCEPTION: DefaultDispatcher-worker-3
    Process: de.datlag.burningseries, PID: 16285
    java.lang.IllegalStateException: Fragment's view can't be accessed. Fragment isn't added
        at by.kirich1409.viewbindingdelegate.LifecycleViewBindingProperty.getValue(ViewBindingProperty.kt:87)
        at by.kirich1409.viewbindingdelegate.FragmentViewBindingProperty.getValue(FragmentViewBindings.kt:63)
        at by.kirich1409.viewbindingdelegate.FragmentViewBindingProperty.getValue(FragmentViewBindings.kt:52)
        at de.datlag.burningseries.ui.fragment.ScrapeHosterFragment.getBinding(ScrapeHosterFragment.kt:34)
        at de.datlag.burningseries.ui.fragment.ScrapeHosterFragment.access$getBinding(ScrapeHosterFragment.kt:29)
        at de.datlag.burningseries.ui.fragment.ScrapeHosterFragment$saveStream$1$1.invokeSuspend(ScrapeHosterFragment.kt:92)
        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:938)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:233)
        at android.app.ActivityThread.main(ActivityThread.java:8030)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:631)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:978)

It says that it cannot access the ScrapeHosterFragment well because thats true, the fragment I navigated to is SeriesFragment not ScrapeHosterFragment

That's the snippet where I navigate back and triggers this issue

binding.backButton.setOnClickListener {
    findNavController().navigate(SaveScrapedDialogDirections.actionSaveScrapedDialogToSeriesFragment(seriesWithInfo = navArgs.seriesWithInfo))
}
kirich1409 commented 2 years ago

View isn't ready and how you want to create instance of ViewBinding?

DatL4g commented 2 years ago

If you want to look at any fragment it's here https://github.com/DATL4G/BurningSeries-Android/tree/master/app/src/main/java/de/datlag/burningseries/ui/fragment

Basically I do everything the same in fragments:

SeriesFragment

I navigate from SeriesFragment to ScrapeHosterFragment

class SeriesFragment : AdvancedFragment(R.layout.fragment_series) {
    private val binding: FragmentSeriesBinding by viewBinding()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        // Access the views here using binding.view.setOnClickListener { /* for example */ }
    }
}

ScrapeHosterFragment

I navigate from ScrapeHosterFragment to SaveScrapedDialog

class ScrapeHosterFragment : AdvancedFragment(R.layout.fragment_scrape_hoster) {
    private val binding: FragmentScrapeHosterBinding by viewBinding()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        // Access the views here
    }
}

SaveScrapedDialog

Navigation explained in comment

class SaveScrapedDialog : BottomSheetDialogFragment() {
    private val binding: DialogSaveScrapedBinding by viewBinding()

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        if (safeContext.packageManager.isTelevision()) {
            dialog?.setOnShowListener {
                it.expand()
            }
        }

        return inflater.inflate(R.layout.dialog_save_scraped, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        binding.continueButton.setOnClickListener {
            dismiss()
        }
        // I navigate here from SaveScrapedDialog to SeriesFragment and it causes the issue on the ScrapeHosterFragment
        binding.backButton.setOnClickListener {
            findNavController().navigate(SaveScrapedDialogDirections.actionSaveScrapedDialogToSeriesFragment(seriesWithInfo = navArgs.seriesWithInfo))
        }
    }
}
kirich1409 commented 2 years ago

You execute Coroutine in saveStream() on background thread, and it has no guarantee to be executed when Fragment is attached or not destroyed. Before call UI check that

 private fun saveStream() = lifecycleScope.launch(Dispatchers.IO) {
        val scrapeJsInput = safeContext.resources.openRawResource(R.raw.scrape_hoster)
        val jsText = String(scrapeJsInput.readBytes())
        while (true) {
            if (getView() != null) {
               withContext(Dispatchers.Main) {
                   binding.webView.evaluateJavascript(jsText) {
                       if (it != null && it.isNotEmpty() && !it.equals("null", true)) {
                           saveScrapedData(it)
                       } 
                   }
               }
               delay(3000)
            } else {
               // Do something when view has been destroyed
            }
        }
    }
kirich1409 commented 2 years ago

Or you can run coroutine attached to viewLifecycle. Read more about that here.