diff --git a/app/build.gradle b/app/build.gradle index 105e1c331..1cc098041 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -353,7 +353,8 @@ dependencies { implementation Deps.androidx_navigation_fragment implementation Deps.androidx_navigation_ui implementation Deps.androidx_recyclerview - implementation Deps.androidx_lifecycle_viewmodel_ktx + implementation Deps.androidx_lifecycle_runtime + implementation Deps.androidx_lifecycle_viewmodel implementation Deps.androidx_lifecycle_viewmodel_ss implementation Deps.androidx_core implementation Deps.androidx_core_ktx diff --git a/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt b/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt index 5350d1ff4..76d68e998 100644 --- a/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt @@ -23,14 +23,13 @@ import androidx.core.net.toUri import androidx.fragment.app.Fragment import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProviders +import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.NavHostFragment.findNavController import com.google.android.material.snackbar.Snackbar import kotlinx.android.synthetic.main.component_search.* import kotlinx.android.synthetic.main.fragment_browser.* import kotlinx.android.synthetic.main.fragment_browser.view.* import kotlinx.android.synthetic.main.fragment_search.* -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.Job @@ -100,10 +99,9 @@ import org.mozilla.fenix.utils.ItsNotBrokenSnack import org.mozilla.fenix.utils.Settings import java.net.MalformedURLException import java.net.URL -import kotlin.coroutines.CoroutineContext @SuppressWarnings("TooManyFunctions", "LargeClass") -class BrowserFragment : Fragment(), BackHandler, CoroutineScope { +class BrowserFragment : Fragment(), BackHandler { private lateinit var toolbarComponent: ToolbarComponent @@ -125,12 +123,9 @@ class BrowserFragment : Fragment(), BackHandler, CoroutineScope { private val swipeRefreshFeature = ViewBoundFeatureWrapper() private val customTabsIntegration = ViewBoundFeatureWrapper() private var findBookmarkJob: Job? = null - private lateinit var job: Job var customTabSessionId: String? = null - override val coroutineContext: CoroutineContext get() = Dispatchers.IO + job - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // Disabled while awaiting a better solution to #3209 @@ -139,7 +134,6 @@ class BrowserFragment : Fragment(), BackHandler, CoroutineScope { // TransitionInflater.from(context).inflateTransition(android.R.transition.move).setDuration( // SHARED_TRANSITION_MS // ) - job = Job() } @SuppressWarnings("ComplexMethod") @@ -564,9 +558,9 @@ class BrowserFragment : Fragment(), BackHandler, CoroutineScope { private fun bookmarkTapped() { getSessionById()?.let { session -> - CoroutineScope(IO).launch { - val components = requireComponents - val existing = components.core.bookmarksStorage.getBookmarksWithUrl(session.url) + lifecycleScope.launch(IO) { + val bookmarksStorage = requireComponents.core.bookmarksStorage + val existing = bookmarksStorage.getBookmarksWithUrl(session.url) val found = existing.isNotEmpty() && existing[0].url == session.url if (found) { launch(Main) { @@ -577,13 +571,12 @@ class BrowserFragment : Fragment(), BackHandler, CoroutineScope { ) } } else { - val guid = components.core.bookmarksStorage - .addItem( - BookmarkRoot.Mobile.id, - session.url, - session.title, - null - ) + val guid = bookmarksStorage.addItem( + BookmarkRoot.Mobile.id, + session.url, + session.title, + null + ) launch(Main) { getManagedEmitter() .onNext(QuickActionChange.BookmarkedStateChange(true)) @@ -685,11 +678,6 @@ class BrowserFragment : Fragment(), BackHandler, CoroutineScope { promptsFeature.withFeature { it.onActivityResult(requestCode, resultCode, data) } } - override fun onDestroy() { - super.onDestroy() - job.cancel() - } - // This method triggers the complexity warning. However it's actually not that hard to understand. @SuppressWarnings("ComplexMethod") private fun trackToolbarItemInteraction(action: SearchAction.ToolbarMenuItemTapped) { @@ -824,8 +812,8 @@ class BrowserFragment : Fragment(), BackHandler, CoroutineScope { private fun showQuickSettingsDialog() { val session = getSessionById() ?: return - launch { - val host = session.url.toUri()?.host + lifecycleScope.launch(IO) { + val host = session.url.toUri().host val sitePermissions: SitePermissions? = host?.let { val storage = requireContext().components.core.permissionStorage storage.findSitePermissionsBy(it) @@ -923,7 +911,7 @@ class BrowserFragment : Fragment(), BackHandler, CoroutineScope { private fun updateBookmarkState(session: Session) { if (findBookmarkJob?.isActive == true) findBookmarkJob?.cancel() - findBookmarkJob = launch { + findBookmarkJob = lifecycleScope.launch(IO) { val found = findBookmarkedURL(session) launch(Main) { getManagedEmitter() diff --git a/app/src/main/java/org/mozilla/fenix/collections/CreateCollectionFragment.kt b/app/src/main/java/org/mozilla/fenix/collections/CreateCollectionFragment.kt index 2413c52a6..46abd3322 100644 --- a/app/src/main/java/org/mozilla/fenix/collections/CreateCollectionFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/collections/CreateCollectionFragment.kt @@ -11,28 +11,23 @@ import android.view.View import android.view.ViewGroup import androidx.fragment.app.DialogFragment import androidx.lifecycle.ViewModelProviders +import androidx.lifecycle.lifecycleScope import kotlinx.android.synthetic.main.fragment_create_collection.view.* -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job import kotlinx.coroutines.launch import org.mozilla.fenix.FenixViewModelProvider import org.mozilla.fenix.R +import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.requireComponents import org.mozilla.fenix.home.sessioncontrol.toSessionBundle import org.mozilla.fenix.mvi.ActionBusFactory import org.mozilla.fenix.mvi.getAutoDisposeObservable import org.mozilla.fenix.mvi.getManagedEmitter -import kotlin.coroutines.CoroutineContext -class CreateCollectionFragment : DialogFragment(), CoroutineScope { +class CreateCollectionFragment : DialogFragment() { private lateinit var collectionCreationComponent: CollectionCreationComponent - private lateinit var job: Job private lateinit var viewModel: CreateCollectionViewModel - override val coroutineContext: CoroutineContext - get() = Dispatchers.Main + job - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) isCancelable = false @@ -44,7 +39,6 @@ class CreateCollectionFragment : DialogFragment(), CoroutineScope { container: ViewGroup?, savedInstanceState: Bundle? ): View? { - job = Job() val view = inflater.inflate(R.layout.fragment_create_collection, container, false) viewModel = activity!!.run { @@ -86,11 +80,6 @@ class CreateCollectionFragment : DialogFragment(), CoroutineScope { subscribeToActions() } - override fun onDestroyView() { - super.onDestroyView() - job.cancel() - } - @Suppress("ComplexMethod") private fun subscribeToActions() { getAutoDisposeObservable().subscribe { @@ -129,8 +118,8 @@ class CreateCollectionFragment : DialogFragment(), CoroutineScope { context?.let { context -> val sessionBundle = it.tabs.toList().toSessionBundle(context) - launch(Dispatchers.IO) { - requireComponents.core.tabCollectionStorage.createCollection(it.name, sessionBundle) + viewLifecycleOwner.lifecycleScope.launch(Dispatchers.IO) { + context.components.core.tabCollectionStorage.createCollection(it.name, sessionBundle) } } } @@ -138,15 +127,15 @@ class CreateCollectionFragment : DialogFragment(), CoroutineScope { dismiss() context?.let { context -> val sessionBundle = it.tabs.toList().toSessionBundle(context) - launch(Dispatchers.IO) { - requireComponents.core.tabCollectionStorage + viewLifecycleOwner.lifecycleScope.launch(Dispatchers.IO) { + context.components.core.tabCollectionStorage .addTabsToCollection(it.collection, sessionBundle) } } } is CollectionCreationAction.RenameCollection -> { dismiss() - launch(Dispatchers.IO) { + viewLifecycleOwner.lifecycleScope.launch(Dispatchers.IO) { requireComponents.core.tabCollectionStorage.renameCollection(it.collection, it.name) } } diff --git a/app/src/main/java/org/mozilla/fenix/exceptions/ExceptionsFragment.kt b/app/src/main/java/org/mozilla/fenix/exceptions/ExceptionsFragment.kt index 5c8e27842..1a587378e 100644 --- a/app/src/main/java/org/mozilla/fenix/exceptions/ExceptionsFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/exceptions/ExceptionsFragment.kt @@ -10,11 +10,11 @@ import android.view.View import android.view.ViewGroup import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment +import androidx.lifecycle.lifecycleScope import androidx.navigation.Navigation import kotlinx.android.synthetic.main.fragment_exceptions.view.* -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job +import kotlinx.coroutines.Dispatchers.IO +import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch import org.mozilla.fenix.FenixViewModelProvider @@ -22,19 +22,10 @@ import org.mozilla.fenix.R import org.mozilla.fenix.mvi.ActionBusFactory import org.mozilla.fenix.mvi.getAutoDisposeObservable import org.mozilla.fenix.mvi.getManagedEmitter -import kotlin.coroutines.CoroutineContext -class ExceptionsFragment : Fragment(), CoroutineScope { - private var job = Job() - override val coroutineContext: CoroutineContext - get() = job + Dispatchers.Main +class ExceptionsFragment : Fragment() { private lateinit var exceptionsComponent: ExceptionsComponent - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - job = Job() - } - override fun onResume() { super.onResume() activity?.title = getString(R.string.preference_exceptions) @@ -65,14 +56,14 @@ class ExceptionsFragment : Fragment(), CoroutineScope { getAutoDisposeObservable() .subscribe { when (it) { - is ExceptionsAction.Delete.All -> launch(Dispatchers.IO) { + is ExceptionsAction.Delete.All -> viewLifecycleOwner.lifecycleScope.launch(IO) { val domains = ExceptionDomains.load(context!!) ExceptionDomains.remove(context!!, domains) - launch(Dispatchers.Main) { - view?.let { view: View -> Navigation.findNavController(view).navigateUp() } + launch(Main) { + view?.let { view -> Navigation.findNavController(view).navigateUp() } } } - is ExceptionsAction.Delete.One -> launch(Dispatchers.IO) { + is ExceptionsAction.Delete.One -> viewLifecycleOwner.lifecycleScope.launch(IO) { ExceptionDomains.remove(context!!, listOf(it.item.url)) reloadData() } @@ -93,7 +84,7 @@ class ExceptionsFragment : Fragment(), CoroutineScope { val items = loadAndMapExceptions() coroutineScope { - launch(Dispatchers.Main) { + launch(Main) { if (items.isEmpty()) { view?.let { view: View -> Navigation.findNavController(view).navigateUp() } return@launch diff --git a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt index 736d1a792..fd5a82033 100644 --- a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt @@ -5,7 +5,6 @@ package org.mozilla.fenix.home import android.animation.Animator -import android.content.Context import android.content.DialogInterface import android.content.res.Resources import android.graphics.drawable.BitmapDrawable @@ -14,12 +13,14 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.view.ViewTreeObserver +import androidx.annotation.StringRes import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import androidx.constraintlayout.widget.ConstraintLayout.LayoutParams.PARENT_ID import androidx.fragment.app.Fragment import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProviders +import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.NavHostFragment.findNavController import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView @@ -27,9 +28,7 @@ import androidx.recyclerview.widget.RecyclerView.SCROLL_STATE_IDLE import com.google.android.material.snackbar.Snackbar import kotlinx.android.synthetic.main.fragment_home.* import kotlinx.android.synthetic.main.fragment_home.view.* -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking @@ -79,11 +78,10 @@ import org.mozilla.fenix.onboarding.FenixOnboarding import org.mozilla.fenix.settings.SupportUtils import org.mozilla.fenix.share.ShareTab import org.mozilla.fenix.utils.allowUndo -import kotlin.coroutines.CoroutineContext import kotlin.math.roundToInt @SuppressWarnings("TooManyFunctions", "LargeClass") -class HomeFragment : Fragment(), CoroutineScope, AccountObserver { +class HomeFragment : Fragment(), AccountObserver { private val bus = ActionBusFactory.get(this) private var tabCollectionObserver: Observer>? = null @@ -110,10 +108,6 @@ class HomeFragment : Fragment(), CoroutineScope, AccountObserver { private val onboarding by lazy { FenixOnboarding(requireContext()) } private lateinit var sessionControlComponent: SessionControlComponent - private lateinit var job: Job - override val coroutineContext: CoroutineContext - get() = Dispatchers.Main + job - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // Disabled while awaiting a better solution to #3209 @@ -135,7 +129,6 @@ class HomeFragment : Fragment(), CoroutineScope, AccountObserver { container: ViewGroup?, savedInstanceState: Bundle? ): View? { - job = Job() val view = inflater.inflate(R.layout.fragment_home, container, false) val mode = currentMode() @@ -177,7 +170,7 @@ class HomeFragment : Fragment(), CoroutineScope, AccountObserver { val listener = object : ViewTreeObserver.OnPreDrawListener { override fun onPreDraw(): Boolean { - launch { + viewLifecycleOwner.lifecycleScope.launch { delay(ANIM_SCROLL_DELAY) restoreLayoutState() startPostponedEnterTransition() @@ -209,7 +202,7 @@ class HomeFragment : Fragment(), CoroutineScope, AccountObserver { setupHomeMenu() - launch(Dispatchers.Default) { + viewLifecycleOwner.lifecycleScope.launch(Dispatchers.Default) { val iconSize = resources.getDimension(R.dimen.preference_icon_drawable_size).toInt() val searchIcon = requireComponents.search.searchEngineManager.getDefaultSearchEngine( @@ -274,7 +267,6 @@ class HomeFragment : Fragment(), CoroutineScope, AccountObserver { override fun onDestroyView() { homeMenu = null - job.cancel() super.onDestroyView() } @@ -359,7 +351,7 @@ class HomeFragment : Fragment(), CoroutineScope, AccountObserver { removeTabWithUndo(action.sessionId) } else { pendingSessionDeletion?.deletionJob?.let { - launch { + viewLifecycleOwner.lifecycleScope.launch { it.invoke() }.invokeOnCompletion { pendingSessionDeletion = null @@ -379,7 +371,7 @@ class HomeFragment : Fragment(), CoroutineScope, AccountObserver { sessionManager.filteredSessions(action.private) ) else { pendingSessionDeletion?.deletionJob?.let { - launch { + viewLifecycleOwner.lifecycleScope.launch { it.invoke() }.invokeOnCompletion { pendingSessionDeletion = null @@ -413,7 +405,7 @@ class HomeFragment : Fragment(), CoroutineScope, AccountObserver { private fun invokePendingDeleteJobs() { pendingSessionDeletion?.deletionJob?.let { - launch { + viewLifecycleOwner.lifecycleScope.launch { it.invoke() }.invokeOnCompletion { pendingSessionDeletion = null @@ -421,7 +413,7 @@ class HomeFragment : Fragment(), CoroutineScope, AccountObserver { } deleteAllSessionsJob?.let { - launch { + viewLifecycleOwner.lifecycleScope.launch { it.invoke() }.invokeOnCompletion { deleteAllSessionsJob = null @@ -438,7 +430,7 @@ class HomeFragment : Fragment(), CoroutineScope, AccountObserver { dialog.cancel() } setPositiveButton(R.string.tab_collection_dialog_positive) { dialog: DialogInterface, _ -> - launch(Dispatchers.IO) { + viewLifecycleOwner.lifecycleScope.launch(Dispatchers.IO) { requireComponents.core.tabCollectionStorage.removeCollection(tabCollection) }.invokeOnCompletion { dialog.dismiss() @@ -517,7 +509,7 @@ class HomeFragment : Fragment(), CoroutineScope, AccountObserver { ) } } - launch { + viewLifecycleOwner.lifecycleScope.launch { delay(ANIM_SCROLL_DELAY) sessionControlComponent.view.smoothScrollToPosition(0) } @@ -527,7 +519,7 @@ class HomeFragment : Fragment(), CoroutineScope, AccountObserver { share(tabs = shareTabs) } is CollectionAction.RemoveTab -> { - launch(Dispatchers.IO) { + viewLifecycleOwner.lifecycleScope.launch(Dispatchers.IO) { requireComponents.core.tabCollectionStorage.removeTabFromCollection(action.collection, action.tab) } } @@ -608,7 +600,7 @@ class HomeFragment : Fragment(), CoroutineScope, AccountObserver { } deleteAllSessionsJob = deleteOperation - allowUndo( + viewLifecycleOwner.lifecycleScope.allowUndo( view!!, getString(R.string.snackbar_tabs_deleted), getString(R.string.snackbar_deleted_undo), { @@ -631,7 +623,7 @@ class HomeFragment : Fragment(), CoroutineScope, AccountObserver { pendingSessionDeletion = PendingSessionDeletion(deleteOperation, sessionId) - allowUndo( + viewLifecycleOwner.lifecycleScope.allowUndo( view!!, getString(R.string.snackbar_tab_deleted), getString(R.string.snackbar_deleted_undo), { @@ -732,7 +724,7 @@ class HomeFragment : Fragment(), CoroutineScope, AccountObserver { } private fun scrollAndAnimateCollection(tabsAddedToCollectionSize: Int, changedCollection: TabCollection? = null) { - launch { + viewLifecycleOwner.lifecycleScope.launch { val recyclerView = sessionControlComponent.view delay(ANIM_SCROLL_DELAY) val tabsSize = getListOfSessions().size @@ -768,7 +760,7 @@ class HomeFragment : Fragment(), CoroutineScope, AccountObserver { } private fun animateCollection(addedTabsSize: Int, indexOfCollection: Int) { - launch { + viewLifecycleOwner.lifecycleScope.launch { val viewHolder = sessionControlComponent.view.findViewHolderForAdapterPosition(indexOfCollection) val border = (viewHolder as? CollectionViewHolder)?.view?.findViewById(R.id.selected_border) val listener = object : Animator.AnimatorListener { @@ -815,27 +807,24 @@ class HomeFragment : Fragment(), CoroutineScope, AccountObserver { } private fun showSavedSnackbar(tabSize: Int) { - launch { + viewLifecycleOwner.lifecycleScope.launch { delay(ANIM_SNACKBAR_DELAY) - context?.let { context: Context -> - view?.let { view: View -> - val string = - if (tabSize > 1) context.getString(R.string.create_collection_tabs_saved) else - context.getString(R.string.create_collection_tab_saved) - val snackbar = FenixSnackbar.make(view, Snackbar.LENGTH_LONG).setText(string) - snackbar.show() + view?.let { view -> + @StringRes + val stringRes = if (tabSize > 1) { + R.string.create_collection_tabs_saved + } else { + R.string.create_collection_tab_saved } + FenixSnackbar.make(view, Snackbar.LENGTH_LONG).setText(view.context.getString(stringRes)).show() } } } private fun showRenamedSnackbar() { - context?.let { context: Context -> - view?.let { view: View -> - val string = context.getString(R.string.snackbar_collection_renamed) - FenixSnackbar.make(view, Snackbar.LENGTH_LONG).setText(string) - .show() - } + view?.let { view -> + val string = view.context.getString(R.string.snackbar_collection_renamed) + FenixSnackbar.make(view, Snackbar.LENGTH_LONG).setText(string).show() } } diff --git a/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkFragment.kt b/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkFragment.kt index d65a8456c..a13e730a3 100644 --- a/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkFragment.kt @@ -21,10 +21,10 @@ import androidx.core.content.ContextCompat import androidx.core.content.getSystemService import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProviders +import androidx.lifecycle.lifecycleScope import androidx.navigation.NavController import androidx.navigation.Navigation import kotlinx.android.synthetic.main.fragment_bookmark.view.* -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.Job @@ -55,12 +55,10 @@ import org.mozilla.fenix.mvi.ActionBusFactory import org.mozilla.fenix.mvi.getAutoDisposeObservable import org.mozilla.fenix.mvi.getManagedEmitter import org.mozilla.fenix.utils.allowUndo -import kotlin.coroutines.CoroutineContext @SuppressWarnings("TooManyFunctions", "LargeClass") -class BookmarkFragment : Fragment(), CoroutineScope, BackHandler, AccountObserver { +class BookmarkFragment : Fragment(), BackHandler, AccountObserver { - private lateinit var job: Job private lateinit var bookmarkComponent: BookmarkComponent private lateinit var signInComponent: SignInComponent var currentRoot: BookmarkNode? = null @@ -74,9 +72,6 @@ class BookmarkFragment : Fragment(), CoroutineScope, BackHandler, AccountObserve } lateinit var initialJob: Job - override val coroutineContext: CoroutineContext - get() = Main + job - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { val view = inflater.inflate(R.layout.fragment_bookmark, container, false) bookmarkComponent = BookmarkComponent( @@ -109,7 +104,6 @@ class BookmarkFragment : Fragment(), CoroutineScope, BackHandler, AccountObserve override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - job = Job() activity?.title = getString(R.string.library_bookmarks) setHasOptionsMenu(true) } @@ -126,11 +120,11 @@ class BookmarkFragment : Fragment(), CoroutineScope, BackHandler, AccountObserve } private fun loadInitialBookmarkFolder(currentGuid: String): Job { - return launch(IO) { + return lifecycleScope.launch(IO) { currentRoot = context?.bookmarkStorage()?.getTree(currentGuid).withOptionalDesktopFolders(context) as BookmarkNode - launch(Main) { + lifecycleScope.launch(Main) { getManagedEmitter().onNext(BookmarkChange.Change(currentRoot!!)) activity?.run { @@ -151,7 +145,6 @@ class BookmarkFragment : Fragment(), CoroutineScope, BackHandler, AccountObserve override fun onDestroy() { super.onDestroy() navigation.removeOnDestinationChangedListener(onDestinationChangedListener) - job.cancel() } override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { @@ -260,7 +253,7 @@ class BookmarkFragment : Fragment(), CoroutineScope, BackHandler, AccountObserve getManagedEmitter() .onNext(BookmarkChange.Change(currentRoot - it.item.guid)) - allowUndo( + lifecycleScope.allowUndo( view!!, getString( R.string.bookmark_deletion_snackbar_message, @@ -340,7 +333,7 @@ class BookmarkFragment : Fragment(), CoroutineScope, BackHandler, AccountObserve val selectedBookmarks = getSelectedBookmarks() getManagedEmitter().onNext(BookmarkChange.Change(currentRoot - selectedBookmarks)) - allowUndo( + lifecycleScope.allowUndo( view!!, getString(R.string.bookmark_deletion_multiple_snackbar_message), getString(R.string.bookmark_undo_deletion), { refreshBookmarks() } ) { @@ -359,7 +352,7 @@ class BookmarkFragment : Fragment(), CoroutineScope, BackHandler, AccountObserve override fun onAuthenticated(account: OAuthAccount) { getManagedEmitter().onNext(SignInChange.SignedIn) - launch { + lifecycleScope.launch { refreshBookmarks() } } diff --git a/app/src/main/java/org/mozilla/fenix/library/bookmarks/addfolder/AddBookmarkFolderFragment.kt b/app/src/main/java/org/mozilla/fenix/library/bookmarks/addfolder/AddBookmarkFolderFragment.kt index 2d3f87b58..c1c226e5c 100644 --- a/app/src/main/java/org/mozilla/fenix/library/bookmarks/addfolder/AddBookmarkFolderFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/library/bookmarks/addfolder/AddBookmarkFolderFragment.kt @@ -15,14 +15,12 @@ import android.view.View import android.view.ViewGroup import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment -import androidx.lifecycle.ViewModelProviders +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.lifecycleScope import androidx.navigation.Navigation import kotlinx.android.synthetic.main.fragment_add_bookmark_folder.* -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.Main -import kotlinx.coroutines.Job import kotlinx.coroutines.launch import mozilla.appservices.places.BookmarkRoot import org.mozilla.fenix.R @@ -31,23 +29,14 @@ import org.mozilla.fenix.ext.getColorFromAttr import org.mozilla.fenix.ext.nav import org.mozilla.fenix.ext.requireComponents import org.mozilla.fenix.library.bookmarks.BookmarksSharedViewModel -import kotlin.coroutines.CoroutineContext -class AddBookmarkFolderFragment : Fragment(), CoroutineScope { +class AddBookmarkFolderFragment : Fragment() { - private lateinit var sharedViewModel: BookmarksSharedViewModel - private lateinit var job: Job - - override val coroutineContext: CoroutineContext - get() = Dispatchers.Main + job + private val sharedViewModel: BookmarksSharedViewModel by activityViewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - job = Job() setHasOptionsMenu(true) - sharedViewModel = activity?.run { - ViewModelProviders.of(this).get(BookmarksSharedViewModel::class.java) - }!! } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { @@ -60,7 +49,7 @@ class AddBookmarkFolderFragment : Fragment(), CoroutineScope { getString(R.string.bookmark_add_folder_fragment_label) (activity as AppCompatActivity).supportActionBar?.show() - launch(IO) { + lifecycleScope.launch(IO) { sharedViewModel.selectedFolder = sharedViewModel.selectedFolder ?: requireComponents.core.bookmarksStorage.getTree(BookmarkRoot.Mobile.id) launch(Main) { @@ -79,11 +68,6 @@ class AddBookmarkFolderFragment : Fragment(), CoroutineScope { } } - override fun onDestroy() { - super.onDestroy() - job.cancel() - } - override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { inflater.inflate(R.menu.bookmarks_add_folder, menu) menu.findItem(R.id.confirm_add_folder_button).icon.colorFilter = @@ -97,7 +81,7 @@ class AddBookmarkFolderFragment : Fragment(), CoroutineScope { bookmark_add_folder_title_edit.error = getString(R.string.bookmark_empty_title_error) return true } - launch(IO) { + lifecycleScope.launch(IO) { val newGuid = requireComponents.core.bookmarksStorage.addFolder( sharedViewModel.selectedFolder!!.guid, bookmark_add_folder_title_edit.text.toString(), null ) diff --git a/app/src/main/java/org/mozilla/fenix/library/bookmarks/edit/EditBookmarkFragment.kt b/app/src/main/java/org/mozilla/fenix/library/bookmarks/edit/EditBookmarkFragment.kt index 2fcce7507..cd27c4e3c 100644 --- a/app/src/main/java/org/mozilla/fenix/library/bookmarks/edit/EditBookmarkFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/library/bookmarks/edit/EditBookmarkFragment.kt @@ -18,7 +18,8 @@ import android.view.ViewGroup import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment -import androidx.lifecycle.ViewModelProviders +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.lifecycleScope import androidx.navigation.Navigation import com.jakewharton.rxbinding3.widget.textChanges import com.uber.autodispose.AutoDispose @@ -28,10 +29,8 @@ import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.functions.BiFunction import io.reactivex.schedulers.Schedulers import kotlinx.android.synthetic.main.fragment_edit_bookmark.* -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.Main -import kotlinx.coroutines.Job import kotlinx.coroutines.launch import mozilla.appservices.places.UrlParseFailed import mozilla.components.concept.storage.BookmarkInfo @@ -46,26 +45,17 @@ import org.mozilla.fenix.ext.setRootTitles import org.mozilla.fenix.ext.withRootTitle import org.mozilla.fenix.library.bookmarks.BookmarksSharedViewModel import java.util.concurrent.TimeUnit -import kotlin.coroutines.CoroutineContext -class EditBookmarkFragment : Fragment(), CoroutineScope { +class EditBookmarkFragment : Fragment() { - private lateinit var sharedViewModel: BookmarksSharedViewModel - private lateinit var job: Job private lateinit var guidToEdit: String + private val sharedViewModel: BookmarksSharedViewModel by activityViewModels() private var bookmarkNode: BookmarkNode? = null private var bookmarkParent: BookmarkNode? = null - override val coroutineContext: CoroutineContext - get() = Main + job - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - job = Job() setHasOptionsMenu(true) - sharedViewModel = activity?.run { - ViewModelProviders.of(this).get(BookmarksSharedViewModel::class.java) - }!! } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { @@ -83,7 +73,7 @@ class EditBookmarkFragment : Fragment(), CoroutineScope { activity?.supportActionBar?.show() guidToEdit = EditBookmarkFragmentArgs.fromBundle(arguments!!).guidToEdit - launch(IO) { + lifecycleScope.launch(IO) { bookmarkNode = requireComponents.core.bookmarksStorage.getTree(guidToEdit) bookmarkParent = sharedViewModel.selectedFolder ?: bookmarkNode?.parentGuid?.let { @@ -149,11 +139,6 @@ class EditBookmarkFragment : Fragment(), CoroutineScope { } } - override fun onDestroy() { - super.onDestroy() - job.cancel() - } - override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { inflater.inflate(R.menu.bookmarks_edit, menu) menu.findItem(R.id.delete_bookmark_button).icon.colorFilter = @@ -178,7 +163,7 @@ class EditBookmarkFragment : Fragment(), CoroutineScope { dialog.cancel() } setPositiveButton(R.string.tab_collection_dialog_positive) { dialog: DialogInterface, _ -> - launch(IO) { + lifecycleScope.launch(IO) { requireComponents.core.bookmarksStorage.deleteNode(guidToEdit) requireComponents.analytics.metrics.track(Event.RemoveBookmark) launch(Main) { @@ -193,7 +178,7 @@ class EditBookmarkFragment : Fragment(), CoroutineScope { } private fun updateBookmarkNode(pair: Pair) { - launch(IO) { + lifecycleScope.launch(IO) { try { requireComponents.let { if (pair != Pair(bookmarkNode?.title, bookmarkNode?.url)) { diff --git a/app/src/main/java/org/mozilla/fenix/library/bookmarks/selectfolder/SelectBookmarkFolderFragment.kt b/app/src/main/java/org/mozilla/fenix/library/bookmarks/selectfolder/SelectBookmarkFolderFragment.kt index bb07def53..9c3d3ffff 100644 --- a/app/src/main/java/org/mozilla/fenix/library/bookmarks/selectfolder/SelectBookmarkFolderFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/library/bookmarks/selectfolder/SelectBookmarkFolderFragment.kt @@ -16,13 +16,12 @@ import android.view.View import android.view.ViewGroup import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment -import androidx.lifecycle.ViewModelProviders +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.lifecycleScope import kotlinx.android.synthetic.main.fragment_select_bookmark_folder.* import kotlinx.android.synthetic.main.fragment_select_bookmark_folder.view.* -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.Main -import kotlinx.coroutines.Job import kotlinx.coroutines.launch import mozilla.appservices.places.BookmarkRoot import mozilla.components.concept.storage.BookmarkNode @@ -47,21 +46,16 @@ import org.mozilla.fenix.library.bookmarks.SignInViewModel import org.mozilla.fenix.mvi.ActionBusFactory import org.mozilla.fenix.mvi.getAutoDisposeObservable import org.mozilla.fenix.mvi.getManagedEmitter -import kotlin.coroutines.CoroutineContext @SuppressWarnings("TooManyFunctions") -class SelectBookmarkFolderFragment : Fragment(), CoroutineScope, AccountObserver { +class SelectBookmarkFolderFragment : Fragment(), AccountObserver { - private lateinit var sharedViewModel: BookmarksSharedViewModel - private lateinit var job: Job + private val sharedViewModel: BookmarksSharedViewModel by activityViewModels() private var folderGuid: String? = null private var bookmarkNode: BookmarkNode? = null private lateinit var signInComponent: SignInComponent - override val coroutineContext: CoroutineContext - get() = Main + job - // Fill out our title map once we have context. override fun onAttach(context: Context) { super.onAttach(context) @@ -70,11 +64,7 @@ class SelectBookmarkFolderFragment : Fragment(), CoroutineScope, AccountObserver override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - job = Job() setHasOptionsMenu(true) - sharedViewModel = activity?.run { - ViewModelProviders.of(this).get(BookmarksSharedViewModel::class.java) - }!! } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { @@ -116,7 +106,7 @@ class SelectBookmarkFolderFragment : Fragment(), CoroutineScope, AccountObserver folderGuid = SelectBookmarkFolderFragmentArgs.fromBundle(arguments!!).folderGuid ?: BookmarkRoot.Root.id checkIfSignedIn() - launch(IO) { + lifecycleScope.launch(IO) { bookmarkNode = requireComponents.core.bookmarksStorage.getTree(BookmarkRoot.Root.id, true) .withOptionalDesktopFolders(context, showMobileRoot = true) @@ -136,11 +126,6 @@ class SelectBookmarkFolderFragment : Fragment(), CoroutineScope, AccountObserver ?: getManagedEmitter().onNext(SignInChange.SignedOut) } - override fun onDestroy() { - super.onDestroy() - job.cancel() - } - override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { val visitedAddBookmark = SelectBookmarkFolderFragmentArgs.fromBundle(arguments!!).visitedAddBookmark if (!visitedAddBookmark) { @@ -153,7 +138,7 @@ class SelectBookmarkFolderFragment : Fragment(), CoroutineScope, AccountObserver override fun onOptionsItemSelected(item: MenuItem): Boolean { return when (item.itemId) { R.id.add_folder_button -> { - launch(Main) { + lifecycleScope.launch(Main) { nav( R.id.bookmarkSelectFolderFragment, SelectBookmarkFolderFragmentDirections diff --git a/app/src/main/java/org/mozilla/fenix/library/history/HistoryFragment.kt b/app/src/main/java/org/mozilla/fenix/library/history/HistoryFragment.kt index dbfedaeef..fbf87fa50 100644 --- a/app/src/main/java/org/mozilla/fenix/library/history/HistoryFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/library/history/HistoryFragment.kt @@ -18,14 +18,11 @@ import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat import androidx.fragment.app.Fragment +import androidx.lifecycle.lifecycleScope import androidx.navigation.Navigation import kotlinx.android.synthetic.main.fragment_history.view.* -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers.Main -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.MainScope -import kotlinx.coroutines.cancel import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import mozilla.components.concept.storage.VisitType @@ -47,7 +44,7 @@ import org.mozilla.fenix.share.ShareTab import java.util.concurrent.TimeUnit @SuppressWarnings("TooManyFunctions") -class HistoryFragment : Fragment(), CoroutineScope by MainScope(), BackHandler { +class HistoryFragment : Fragment(), BackHandler { private lateinit var historyComponent: HistoryComponent private val navigation by lazy { Navigation.findNavController(requireView()) } @@ -78,7 +75,7 @@ class HistoryFragment : Fragment(), CoroutineScope by MainScope(), BackHandler { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - launch { reloadData() } + lifecycleScope.launch { reloadData() } } override fun onStart() { @@ -95,11 +92,6 @@ class HistoryFragment : Fragment(), CoroutineScope by MainScope(), BackHandler { } } - override fun onDestroy() { - coroutineContext.cancel() - super.onDestroy() - } - override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { val mode = (historyComponent.uiView as HistoryUIView).mode when (mode) { @@ -144,7 +136,7 @@ class HistoryFragment : Fragment(), CoroutineScope by MainScope(), BackHandler { val components = context?.applicationContext?.components!! val selectedHistory = (historyComponent.uiView as HistoryUIView).getSelected() - GlobalScope.launch(Main) { + lifecycleScope.launch(Main) { deleteSelectedHistory(selectedHistory, components) reloadData() } @@ -200,13 +192,13 @@ class HistoryFragment : Fragment(), CoroutineScope by MainScope(), BackHandler { emitChange { HistoryChange.ExitEditMode } is HistoryAction.Delete.All -> displayDeleteAllDialog() - is HistoryAction.Delete.One -> launch { + is HistoryAction.Delete.One -> lifecycleScope.launch { requireComponents.core .historyStorage .deleteVisit(action.item.url, action.item.visitedAt) reloadData() } - is HistoryAction.Delete.Some -> launch { + is HistoryAction.Delete.Some -> lifecycleScope.launch { val storage = requireComponents.core.historyStorage for (item in action.items) { storage.deleteVisit(item.url, item.visitedAt) @@ -235,7 +227,7 @@ class HistoryFragment : Fragment(), CoroutineScope by MainScope(), BackHandler { } setPositiveButton(R.string.history_clear_dialog) { dialog: DialogInterface, _ -> emitChange { HistoryChange.EnterDeletionMode } - launch { + lifecycleScope.launch { requireComponents.core.historyStorage.deleteEverything() reloadData() launch(Dispatchers.Main) { diff --git a/app/src/main/java/org/mozilla/fenix/utils/Undo.kt b/app/src/main/java/org/mozilla/fenix/utils/Undo.kt index fa0773e34..4437be9b6 100644 --- a/app/src/main/java/org/mozilla/fenix/utils/Undo.kt +++ b/app/src/main/java/org/mozilla/fenix/utils/Undo.kt @@ -26,15 +26,13 @@ internal const val UNDO_DELAY = 3000L * @param onCancel A suspend block to execute in case of cancellation. * @param operation A suspend block to execute if user doesn't cancel via the displayed [FenixSnackbar]. */ -fun allowUndo( +fun CoroutineScope.allowUndo( view: View, message: String, undoActionTitle: String, onCancel: suspend () -> Unit = {}, operation: suspend () -> Unit ) { - val mainScope = CoroutineScope(Dispatchers.Main) - // By using an AtomicBoolean, we achieve memory effects of reading and // writing a volatile variable. val requestedUndo = AtomicBoolean(false) @@ -45,7 +43,7 @@ fun allowUndo( .setText(message) .setAction(undoActionTitle) { requestedUndo.set(true) - mainScope.launch { + launch { onCancel.invoke() } } @@ -55,7 +53,7 @@ fun allowUndo( // Wait a bit, and if user didn't request cancellation, proceed with // requested operation and hide the snackbar. - mainScope.launch { + launch { delay(UNDO_DELAY) if (!requestedUndo.get()) { diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index b8d7a37ed..2910858a5 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -141,14 +141,14 @@ object Deps { const val leanplum = "com.leanplum:leanplum-core:${Versions.leanplum}" const val androidx_annotation = "androidx.annotation:annotation:${Versions.androidx_annotation}" - const val androidx_fragment = "androidx.fragment:fragment:${Versions.androidx_fragment}" + const val androidx_fragment = "androidx.fragment:fragment-ktx:${Versions.androidx_fragment}" const val androidx_appcompat = "androidx.appcompat:appcompat:${Versions.androidx_appcompat}" const val androidx_coordinatorlayout = "androidx.coordinatorlayout:coordinatorlayout:${Versions.androidx_coordinator_layout}" const val androidx_constraintlayout = "androidx.constraintlayout:constraintlayout:${Versions.androidx_constraint_layout}" const val androidx_legacy = "androidx.legacy:legacy-support-v4:${Versions.androidx_legacy}" const val androidx_lifecycle_extensions = "androidx.lifecycle:lifecycle-extensions:${Versions.androidx_lifecycle}" - const val androidx_lifecycle_viewmodel_ktx = "androidx.lifecycle:lifecycle-viewmodel-ktx:${Versions.androidx_lifecycle}" - const val androidx_lifecycle_runtime = "androidx.lifecycle:lifecycle-runtime:${Versions.androidx_lifecycle}" + const val androidx_lifecycle_viewmodel = "androidx.lifecycle:lifecycle-viewmodel-ktx:${Versions.androidx_lifecycle}" + const val androidx_lifecycle_runtime = "androidx.lifecycle:lifecycle-runtime-ktx:${Versions.androidx_lifecycle}" const val androidx_lifecycle_viewmodel_ss = "androidx.lifecycle:lifecycle-viewmodel-savedstate:${Versions.androidx_lifecycle_savedstate}" const val androidx_preference = "androidx.preference:preference-ktx:${Versions.androidx_preference}" const val androidx_safeargs = "androidx.navigation:navigation-safe-args-gradle-plugin:${Versions.androidx_navigation}"