1
0
Fork 0

Fixes #541: Crash on Home Screen

The Android Lifecycle Architecture component does not have fine-grained enough lifecycle event callbacks to safely manage Rx subscriptions in Fragment lifecycles. Added autodispose to simplify.
master
Colin Lee 2019-02-16 19:04:32 -06:00 committed by Jeff Boek
parent 03826651dd
commit 6d71faa44d
8 changed files with 59 additions and 51 deletions

View File

@ -165,6 +165,8 @@ dependencies {
implementation Deps.androidx_fragment implementation Deps.androidx_fragment
implementation Deps.android_arch_navigation implementation Deps.android_arch_navigation
implementation Deps.android_arch_navigation_ui implementation Deps.android_arch_navigation_ui
implementation Deps.autodispose
} }

View File

@ -4,7 +4,6 @@
package org.mozilla.fenix.browser package org.mozilla.fenix.browser
import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
@ -35,16 +34,16 @@ import org.mozilla.fenix.DefaultThemeManager
import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.components.FindInPageIntegration import org.mozilla.fenix.components.FindInPageIntegration
import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.ext.share
import org.mozilla.fenix.mvi.ActionBusFactory
import org.mozilla.fenix.mvi.getSafeManagedObservable
import org.mozilla.fenix.components.toolbar.SearchAction import org.mozilla.fenix.components.toolbar.SearchAction
import org.mozilla.fenix.components.toolbar.SearchState import org.mozilla.fenix.components.toolbar.SearchState
import org.mozilla.fenix.components.toolbar.ToolbarComponent import org.mozilla.fenix.components.toolbar.ToolbarComponent
import org.mozilla.fenix.components.toolbar.ToolbarIntegration import org.mozilla.fenix.components.toolbar.ToolbarIntegration
import org.mozilla.fenix.components.toolbar.ToolbarMenu import org.mozilla.fenix.components.toolbar.ToolbarMenu
import org.mozilla.fenix.components.toolbar.ToolbarUIView import org.mozilla.fenix.components.toolbar.ToolbarUIView
import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.ext.share
import org.mozilla.fenix.mvi.ActionBusFactory
import org.mozilla.fenix.mvi.getAutoDisposeObservable
class BrowserFragment : Fragment(), BackHandler { class BrowserFragment : Fragment(), BackHandler {
private lateinit var toolbarComponent: ToolbarComponent private lateinit var toolbarComponent: ToolbarComponent
@ -101,11 +100,10 @@ class BrowserFragment : Fragment(), BackHandler {
return view return view
} }
@SuppressLint("CheckResult") override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
override fun onCreate(savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState)
super.onCreate(savedInstanceState)
getSafeManagedObservable<SearchAction>() getAutoDisposeObservable<SearchAction>()
.subscribe { .subscribe {
when (it) { when (it) {
is SearchAction.ToolbarTapped -> Navigation.findNavController(toolbar) is SearchAction.ToolbarTapped -> Navigation.findNavController(toolbar)
@ -116,10 +114,6 @@ class BrowserFragment : Fragment(), BackHandler {
is SearchAction.ToolbarMenuItemTapped -> handleToolbarItemInteraction(it) is SearchAction.ToolbarMenuItemTapped -> handleToolbarItemInteraction(it)
} }
} }
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
sessionId = BrowserFragmentArgs.fromBundle(arguments!!).sessionId sessionId = BrowserFragmentArgs.fromBundle(arguments!!).sessionId

View File

@ -4,7 +4,6 @@
package org.mozilla.fenix.home package org.mozilla.fenix.home
import android.annotation.SuppressLint
import android.content.res.Resources import android.content.res.Resources
import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.BitmapDrawable
import android.os.Bundle import android.os.Bundle
@ -20,8 +19,8 @@ import kotlinx.android.synthetic.main.fragment_home.view.*
import mozilla.components.browser.menu.BrowserMenu import mozilla.components.browser.menu.BrowserMenu
import mozilla.components.browser.session.Session import mozilla.components.browser.session.Session
import mozilla.components.browser.session.SessionManager import mozilla.components.browser.session.SessionManager
import org.mozilla.fenix.DefaultThemeManager
import org.mozilla.fenix.BrowsingModeManager import org.mozilla.fenix.BrowsingModeManager
import org.mozilla.fenix.DefaultThemeManager
import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.ext.requireComponents import org.mozilla.fenix.ext.requireComponents
@ -32,8 +31,8 @@ import org.mozilla.fenix.home.tabs.TabsComponent
import org.mozilla.fenix.home.tabs.TabsState import org.mozilla.fenix.home.tabs.TabsState
import org.mozilla.fenix.isPrivate import org.mozilla.fenix.isPrivate
import org.mozilla.fenix.mvi.ActionBusFactory import org.mozilla.fenix.mvi.ActionBusFactory
import org.mozilla.fenix.mvi.getAutoDisposeObservable
import org.mozilla.fenix.mvi.getManagedEmitter import org.mozilla.fenix.mvi.getManagedEmitter
import org.mozilla.fenix.mvi.getSafeManagedObservable
import kotlin.math.roundToInt import kotlin.math.roundToInt
class HomeFragment : Fragment() { class HomeFragment : Fragment() {
@ -57,14 +56,13 @@ class HomeFragment : Fragment() {
return view return view
} }
@SuppressLint("CheckResult")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
(activity as AppCompatActivity).supportActionBar?.hide() (activity as AppCompatActivity).supportActionBar?.hide()
setupHomeMenu() setupHomeMenu()
getSafeManagedObservable<TabsAction>() getAutoDisposeObservable<TabsAction>()
.subscribe { .subscribe {
when (it) { when (it) {
is TabsAction.Select -> { is TabsAction.Select -> {

View File

@ -4,7 +4,6 @@
package org.mozilla.fenix.library.history package org.mozilla.fenix.library.history
import android.annotation.SuppressLint
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.Menu import android.view.Menu
@ -21,13 +20,13 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.mozilla.fenix.HomeActivity
import mozilla.components.support.base.feature.BackHandler import mozilla.components.support.base.feature.BackHandler
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.ext.requireComponents import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.mvi.ActionBusFactory import org.mozilla.fenix.mvi.ActionBusFactory
import org.mozilla.fenix.mvi.getAutoDisposeObservable
import org.mozilla.fenix.mvi.getManagedEmitter import org.mozilla.fenix.mvi.getManagedEmitter
import org.mozilla.fenix.mvi.getSafeManagedObservable
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
class HistoryFragment : Fragment(), CoroutineScope, BackHandler { class HistoryFragment : Fragment(), CoroutineScope, BackHandler {
@ -45,32 +44,15 @@ class HistoryFragment : Fragment(), CoroutineScope, BackHandler {
): View? { ): View? {
val view = inflater.inflate(R.layout.fragment_history, container, false) val view = inflater.inflate(R.layout.fragment_history, container, false)
historyComponent = HistoryComponent(view.history_layout, ActionBusFactory.get(this)) historyComponent = HistoryComponent(view.history_layout, ActionBusFactory.get(this))
return view return view
} }
@SuppressLint("CheckResult")
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
job = Job() job = Job()
setHasOptionsMenu(true) setHasOptionsMenu(true)
(activity as AppCompatActivity).supportActionBar?.show() (activity as AppCompatActivity).supportActionBar?.show()
getSafeManagedObservable<HistoryAction>()
.subscribe {
when (it) {
is HistoryAction.Select -> selectItem(it.item)
is HistoryAction.EnterEditMode -> getManagedEmitter<HistoryChange>()
.onNext(HistoryChange.EnterEditMode(it.item))
is HistoryAction.AddItemForRemoval -> getManagedEmitter<HistoryChange>()
.onNext(HistoryChange.AddItemForRemoval(it.item))
is HistoryAction.RemoveItemForRemoval -> getManagedEmitter<HistoryChange>()
.onNext(HistoryChange.RemoveItemForRemoval(it.item))
is HistoryAction.BackPressed -> getManagedEmitter<HistoryChange>()
.onNext(HistoryChange.ExitEditMode)
}
}
} }
private fun selectItem(item: HistoryItem) { private fun selectItem(item: HistoryItem) {
@ -97,14 +79,27 @@ class HistoryFragment : Fragment(), CoroutineScope, BackHandler {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
val eventEmitter = ActionBusFactory.get(this) getAutoDisposeObservable<HistoryAction>()
.subscribe {
when (it) {
is HistoryAction.Select -> selectItem(it.item)
is HistoryAction.EnterEditMode -> getManagedEmitter<HistoryChange>()
.onNext(HistoryChange.EnterEditMode(it.item))
is HistoryAction.AddItemForRemoval -> getManagedEmitter<HistoryChange>()
.onNext(HistoryChange.AddItemForRemoval(it.item))
is HistoryAction.RemoveItemForRemoval -> getManagedEmitter<HistoryChange>()
.onNext(HistoryChange.RemoveItemForRemoval(it.item))
is HistoryAction.BackPressed -> getManagedEmitter<HistoryChange>()
.onNext(HistoryChange.ExitEditMode)
}
}
launch(Dispatchers.IO) { launch(Dispatchers.IO) {
val items = requireComponents.core.historyStorage.getVisited() val items = requireComponents.core.historyStorage.getVisited()
.mapIndexed { id, item -> HistoryItem(id, item) } .mapIndexed { id, item -> HistoryItem(id, item) }
launch(Dispatchers.Main) { launch(Dispatchers.Main) {
eventEmitter.emit(HistoryChange::class.java, HistoryChange.Change(items)) getManagedEmitter<HistoryChange>().onNext(HistoryChange.Change(items))
} }
} }
} }

View File

@ -4,7 +4,6 @@
package org.mozilla.fenix.search package org.mozilla.fenix.search
import android.annotation.SuppressLint
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
@ -15,17 +14,17 @@ import androidx.navigation.Navigation
import kotlinx.android.synthetic.main.fragment_search.view.* import kotlinx.android.synthetic.main.fragment_search.view.*
import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.mvi.ActionBusFactory
import org.mozilla.fenix.mvi.getManagedEmitter
import org.mozilla.fenix.mvi.getSafeManagedObservable
import org.mozilla.fenix.search.awesomebar.AwesomeBarAction
import org.mozilla.fenix.search.awesomebar.AwesomeBarChange
import org.mozilla.fenix.search.awesomebar.AwesomeBarComponent
import org.mozilla.fenix.search.awesomebar.AwesomeBarState
import org.mozilla.fenix.components.toolbar.SearchAction import org.mozilla.fenix.components.toolbar.SearchAction
import org.mozilla.fenix.components.toolbar.SearchState import org.mozilla.fenix.components.toolbar.SearchState
import org.mozilla.fenix.components.toolbar.ToolbarComponent import org.mozilla.fenix.components.toolbar.ToolbarComponent
import org.mozilla.fenix.components.toolbar.ToolbarUIView import org.mozilla.fenix.components.toolbar.ToolbarUIView
import org.mozilla.fenix.mvi.ActionBusFactory
import org.mozilla.fenix.mvi.getAutoDisposeObservable
import org.mozilla.fenix.mvi.getManagedEmitter
import org.mozilla.fenix.search.awesomebar.AwesomeBarAction
import org.mozilla.fenix.search.awesomebar.AwesomeBarChange
import org.mozilla.fenix.search.awesomebar.AwesomeBarComponent
import org.mozilla.fenix.search.awesomebar.AwesomeBarState
class SearchFragment : Fragment() { class SearchFragment : Fragment() {
private lateinit var toolbarComponent: ToolbarComponent private lateinit var toolbarComponent: ToolbarComponent
@ -54,7 +53,6 @@ class SearchFragment : Fragment() {
return view return view
} }
@SuppressLint("CheckResult")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
@ -66,7 +64,7 @@ class SearchFragment : Fragment() {
view.toolbar_wrapper.clipToOutline = false view.toolbar_wrapper.clipToOutline = false
getSafeManagedObservable<SearchAction>() getAutoDisposeObservable<SearchAction>()
.subscribe { .subscribe {
when (it) { when (it) {
is SearchAction.UrlCommitted -> transitionToBrowser() is SearchAction.UrlCommitted -> transitionToBrowser()
@ -76,7 +74,7 @@ class SearchFragment : Fragment() {
} }
} }
getSafeManagedObservable<AwesomeBarAction>() getAutoDisposeObservable<AwesomeBarAction>()
.subscribe { .subscribe {
when (it) { when (it) {
is AwesomeBarAction.ItemSelected -> transitionToBrowser() is AwesomeBarAction.ItemSelected -> transitionToBrowser()

View File

@ -37,4 +37,8 @@ dependencies {
implementation Deps.rxAndroid implementation Deps.rxAndroid
implementation Deps.rxKotlin implementation Deps.rxKotlin
implementation Deps.autodispose
implementation Deps.autodispose_android
implementation Deps.autodispose_android_aac
} }

View File

@ -26,6 +26,9 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.OnLifecycleEvent import androidx.lifecycle.OnLifecycleEvent
import com.uber.autodispose.AutoDispose.autoDisposable
import com.uber.autodispose.ObservableSubscribeProxy
import com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvider
import io.reactivex.Observable import io.reactivex.Observable
import io.reactivex.Observer import io.reactivex.Observer
import io.reactivex.rxkotlin.merge import io.reactivex.rxkotlin.merge
@ -103,6 +106,11 @@ class ActionBusFactory private constructor(val owner: LifecycleOwner) {
return if (map[clazz] != null) map[clazz] as Observable<T> else create(clazz) return if (map[clazz] != null) map[clazz] as Observable<T> else create(clazz)
} }
fun <T : Action> getAutoDisposeObservable(clazz: Class<T>): ObservableSubscribeProxy<T> {
return getSafeManagedObservable(clazz)
.`as`(autoDisposable(AndroidLifecycleScopeProvider.from(owner)))
}
fun <T : Action> getManagedEmitter(clazz: Class<T>): Observer<T> { fun <T : Action> getManagedEmitter(clazz: Class<T>): Observer<T> {
return if (map[clazz] != null) map[clazz] as Observer<T> else create(clazz) return if (map[clazz] != null) map[clazz] as Observer<T> else create(clazz)
} }
@ -135,6 +143,9 @@ inline fun <reified T : Action> LifecycleOwner.emit(event: T) =
inline fun <reified T : Action> LifecycleOwner.getSafeManagedObservable(): Observable<T> = inline fun <reified T : Action> LifecycleOwner.getSafeManagedObservable(): Observable<T> =
ActionBusFactory.get(this).getSafeManagedObservable(T::class.java) ActionBusFactory.get(this).getSafeManagedObservable(T::class.java)
inline fun <reified T : Action> LifecycleOwner.getAutoDisposeObservable(): ObservableSubscribeProxy<T> =
ActionBusFactory.get(this).getAutoDisposeObservable(T::class.java)
inline fun <reified T : Action> LifecycleOwner.getManagedEmitter(): Observer<T> = inline fun <reified T : Action> LifecycleOwner.getManagedEmitter(): Observer<T> =
ActionBusFactory.get(this).getManagedEmitter(T::class.java) ActionBusFactory.get(this).getManagedEmitter(T::class.java)

View File

@ -28,6 +28,8 @@ private object Versions {
const val espresso_core = "2.2.2" const val espresso_core = "2.2.2"
const val android_arch_navigation = "1.0.0-beta02" const val android_arch_navigation = "1.0.0-beta02"
const val autodispose = "1.1.0"
} }
@Suppress("unused") @Suppress("unused")
@ -109,5 +111,9 @@ object Deps {
const val android_arch_navigation = "android.arch.navigation:navigation-fragment:${Versions.android_arch_navigation}" const val android_arch_navigation = "android.arch.navigation:navigation-fragment:${Versions.android_arch_navigation}"
const val android_arch_navigation_ui = "android.arch.navigation:navigation-ui:${Versions.android_arch_navigation}" const val android_arch_navigation_ui = "android.arch.navigation:navigation-ui:${Versions.android_arch_navigation}"
const val autodispose = "com.uber.autodispose:autodispose:${Versions.autodispose}"
const val autodispose_android = "com.uber.autodispose:autodispose-android:${Versions.autodispose}"
const val autodispose_android_aac = "com.uber.autodispose:autodispose-android-archcomponents:${Versions.autodispose}"
const val autodispose_android_aac_test = "com.uber.autodispose:autodispose-android-archcomponents-test:${Versions.autodispose}"
} }