Solving WebView Lifecycle Crashes in Android: A Lifecycle-Aware Approach with Sealed Classes and Flow

Dipen Jansari
4 min readNov 30, 2024

--

Introduction

WebViews are a powerful tool in Android development, allowing you to load web content right inside your app. Whether you’re building an in-app browser or integrating third-party content, WebViews can handle it all.

But WebViews come with their own set of quirks. One common headache? Crashes caused by lifecycle mismatches.

If you’ve ever seen an error like this:

This happens because WebView callbacks like onPageStarted and onPageFinished don’t respect the lifecycle of the Fragment they’re running in.

In this blog, we’ll explore:

  1. Why these crashes occur ?
  2. How we can leverage separation of concerns using ViewModel, Kotlin Flow, and sealed classes to handle WebView callbacks safely and cleanly ?

The Problem

When WebViews load content, they use WebViewClient callbacks like:

  • onPageStarted – called when a page starts loading.
  • onPageFinished – called when a page finishes loading.
  • shouldOverrideUrlLoading – called when navigating to a new URL.

These callbacks are asynchronous and don’t know about the lifecycle of your Fragment or Activity.

If they try to update a UI element (like a progress bar) after the Fragment's view has been destroyed, you’ll encounter a crash.

Why WebView Callbacks Aren’t Lifecycle-Aware

  • Independent of the UI Lifecycle: WebView callbacks are tied to the loading process, not the lifecycle of the UI hosting them.
  • UI Updates After Destruction: If a callback tries to update the Fragment after its view has been destroyed, it throws an IllegalStateException.

The Solution: Why ViewModel Helps

To handle WebView callbacks safely, we’ll:

  1. Use a ViewModel to abstract WebView logic from the UI.
  2. Implement sealed classes for WebView events (e.g., onPageStarted, onPageFinished).
  3. Use Kotlin Flow for clean, asynchronous event handling.
  4. Observe these events in the Fragment only when its View is active.

Step-by-Step Implementation

Step 1: Define WebView Events with a Sealed Class

Create a sealed class to represent WebView events:

sealed class WebViewEvent {
data class LoadingComplete(val status: String) : WebViewEvent()
data class Error(val error: String) : WebViewEvent()
data class Next(val url: String, val partner: String) : WebViewEvent()
}

Step 2: Handle WebView Events in the ViewModel

The ViewModel emits these events using MutableSharedFlow, ensuring lifecycle awareness.

class WebViewModel : ViewModel() {
private val _webViewEvents = MutableSharedFlow<WebViewEvent>()
val webViewEvents: SharedFlow<WebViewEvent> = _webViewEvents

fun onLoadingComplete(status: String) {
viewModelScope.launch {
_webViewEvents.emit(WebViewEvent.LoadingComplete(status))
}
}

fun onError(error: String) {
viewModelScope.launch {
_webViewEvents.emit(WebViewEvent.Error(error))
}
}

fun onNext(url: String, partner: String) {
viewModelScope.launch {
_webViewEvents.emit(WebViewEvent.Next(url, partner))
}
}
}

Step 3: Use a Custom WebViewClient

Route WebView events to the ViewModel:

class CustomWebClient(private val viewModel: WebViewModel) : WebViewClient() {
override fun onPageFinished(view: WebView, url: String) {
viewModel.onLoadingComplete("Page Finished Loading")
}

override fun onReceivedError(view: WebView?, request: WebResourceRequest?, error: WebResourceError?) {
viewModel.onError("Error loading page")
}

override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean {
val url = request?.url.toString()
viewModel.onNext(url, "PartnerName")
return true
}
}

Step 4: Observe WebView Events in the Fragment

Use Flow to observe events and update the UI only when the Fragment's view is active.

class WebViewFragment : Fragment() {
private val viewModel: WebViewModel by viewModels()

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

viewLifecycleOwner.lifecycleScope.launch {
viewModel.webViewEvents
.flowWithLifecycle(lifecycle, Lifecycle.State.STARTED)
.collectLatest { event ->
when (event) {
is WebViewEvent.LoadingComplete -> displayContent()
is WebViewEvent.Error -> showToast(event.error)
is WebViewEvent.Next -> loadNextUrl(event.url)
}
}
}
}

private fun displayContent() {
// Show the content after loading
}

private fun loadNextUrl(url: String) {
binding.webView.loadUrl(url)
}
}

Step 4: Clean Up Resources

To ensure no memory leaks, clear the WebView’s client when the Fragment is destroyed.

override fun onDestroyView() {
binding.webView.webViewClient = null
super.onDestroyView()
}
Let the ViewModel handle the mess while the Fragment stays safe.

Final Thoughts

By using ViewModel, Kotlin Flow, and sealed classes, we:

  • Ensure WebView callbacks respect the Fragment lifecycle.
  • Decouple WebView logic from UI updates.
  • Create a robust, maintainable solution for handling WebView events.

This approach not only solves lifecycle issues but also improves the overall architecture of your app. Have you faced similar challenges with WebViews? Share your thoughts and solutions in the comments below!

--

--

Dipen Jansari
Dipen Jansari

Written by Dipen Jansari

Senior Android Engineer @FloBiz | Ex. ZebPay | Ex. OpenXcell

No responses yet