From 3162713849700099fc9a509dcc20627f70c71b04 Mon Sep 17 00:00:00 2001 From: kevingitonga Date: Fri, 1 Jul 2022 16:58:03 +0300 Subject: [PATCH 01/34] Replace builder() with Factory in SingleTypeBuilder and MultiTypeBuilder. --- .../AdministratorControlsFragmentPresenter.kt | 2 +- .../ProfileAndDeviceIdFragmentPresenter.kt | 3 +- .../CompletedStoryListFragmentPresenter.kt | 2 +- .../DeveloperOptionsFragmentPresenter.kt | 2 +- .../ForceNetworkTypeFragmentPresenter.kt | 2 +- .../MarkChaptersCompletedFragmentPresenter.kt | 2 +- .../MarkStoriesCompletedFragmentPresenter.kt | 2 +- .../MarkTopicsCompletedFragmentPresenter.kt | 2 +- .../ViewEventLogsFragmentPresenter.kt | 2 +- .../android/app/help/HelpFragmentPresenter.kt | 2 +- .../app/help/faq/FAQListFragmentPresenter.kt | 2 +- .../LicenseListFragmentPresenter.kt | 2 +- ...irdPartyDependencyListFragmentPresenter.kt | 2 +- ...HintsAndSolutionDialogFragmentPresenter.kt | 2 +- .../android/app/home/HomeFragmentPresenter.kt | 2 +- .../promotedlist/ComingSoonTopicsListView.kt | 5 +- .../promotedlist/PromotedStoryListView.kt | 79 ++++++++++++---- .../onboarding/OnboardingFragmentPresenter.kt | 2 +- .../OngoingTopicListFragmentPresenter.kt | 2 +- .../options/AppLanguageFragmentPresenter.kt | 3 +- .../options/AudioLanguageFragmentPresenter.kt | 3 +- .../app/options/OptionsFragmentPresenter.kt | 2 +- .../ReadingTextSizeFragmentPresenter.kt | 3 +- .../state/DragDropSortInteractionView.kt | 7 +- .../player/state/SelectionInteractionView.kt | 7 +- .../state/StatePlayerRecyclerViewAssembler.kt | 11 ++- .../ProfileChooserFragmentPresenter.kt | 2 +- .../ProfileProgressFragmentPresenter.kt | 2 +- .../app/recyclerview/BindableAdapter.kt | 91 +++++-------------- .../profile/ProfileListFragmentPresenter.kt | 2 +- .../app/story/StoryFragmentPresenter.kt | 2 +- .../testing/DragDropTestActivityPresenter.kt | 3 +- .../lessons/TopicLessonsFragmentPresenter.kt | 4 +- .../TopicPracticeFragmentPresenter.kt | 2 +- .../TopicRevisionFragmentPresenter.kt | 2 +- .../WalkthroughTopicListFragmentPresenter.kt | 2 +- 36 files changed, 134 insertions(+), 133 deletions(-) diff --git a/app/src/main/java/org/oppia/android/app/administratorcontrols/AdministratorControlsFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/administratorcontrols/AdministratorControlsFragmentPresenter.kt index e66d0a1ea0b..da96366f5cf 100644 --- a/app/src/main/java/org/oppia/android/app/administratorcontrols/AdministratorControlsFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/administratorcontrols/AdministratorControlsFragmentPresenter.kt @@ -78,7 +78,7 @@ class AdministratorControlsFragmentPresenter @Inject constructor( private fun createRecyclerViewAdapter(isMultipane: Boolean): BindableAdapter { return BindableAdapter.MultiTypeBuilder - .newBuilder { viewModel -> + .Factory(fragment).create { viewModel -> viewModel.isMultipane.set(isMultipane) when (viewModel) { is AdministratorControlsGeneralViewModel -> { diff --git a/app/src/main/java/org/oppia/android/app/administratorcontrols/learneranalytics/ProfileAndDeviceIdFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/administratorcontrols/learneranalytics/ProfileAndDeviceIdFragmentPresenter.kt index de87f8773e6..41e95e106ba 100644 --- a/app/src/main/java/org/oppia/android/app/administratorcontrols/learneranalytics/ProfileAndDeviceIdFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/administratorcontrols/learneranalytics/ProfileAndDeviceIdFragmentPresenter.kt @@ -39,7 +39,7 @@ class ProfileAndDeviceIdFragmentPresenter @Inject constructor( private fun createRecyclerViewAdapter(): BindableAdapter { return BindableAdapter.MultiTypeBuilder - .newBuilder { viewModel -> + .Factory(fragment).create { viewModel -> when (viewModel) { is DeviceIdItemViewModel -> ProfileListItemViewType.DEVICE_ID is ProfileLearnerIdItemViewModel -> ProfileListItemViewType.LEARNER_ID @@ -47,7 +47,6 @@ class ProfileAndDeviceIdFragmentPresenter @Inject constructor( else -> error("Encountered unexpected view model: $viewModel") } } - .setLifecycleOwner(fragment) .registerViewDataBinder( viewType = ProfileListItemViewType.DEVICE_ID, inflateDataBinding = ProfileListDeviceIdItemBinding::inflate, diff --git a/app/src/main/java/org/oppia/android/app/completedstorylist/CompletedStoryListFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/completedstorylist/CompletedStoryListFragmentPresenter.kt index 660113abfff..bde9e3052af 100644 --- a/app/src/main/java/org/oppia/android/app/completedstorylist/CompletedStoryListFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/completedstorylist/CompletedStoryListFragmentPresenter.kt @@ -53,7 +53,7 @@ class CompletedStoryListFragmentPresenter @Inject constructor( private fun createRecyclerViewAdapter(): BindableAdapter { return BindableAdapter.SingleTypeBuilder - .newBuilder() + .Factory(fragment).create() .registerViewDataBinderWithSameModelType( inflateDataBinding = CompletedStoryItemBinding::inflate, setViewModel = CompletedStoryItemBinding::setViewModel diff --git a/app/src/main/java/org/oppia/android/app/devoptions/DeveloperOptionsFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/devoptions/DeveloperOptionsFragmentPresenter.kt index 5fab6646fa4..2707ebf15fa 100644 --- a/app/src/main/java/org/oppia/android/app/devoptions/DeveloperOptionsFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/devoptions/DeveloperOptionsFragmentPresenter.kt @@ -60,7 +60,7 @@ class DeveloperOptionsFragmentPresenter @Inject constructor( private fun createRecyclerViewAdapter(): BindableAdapter { return BindableAdapter.MultiTypeBuilder - .newBuilder { viewModel -> + .Factory(fragment).create { viewModel -> when (viewModel) { is DeveloperOptionsModifyLessonProgressViewModel -> { viewModel.itemIndex.set(0) diff --git a/app/src/main/java/org/oppia/android/app/devoptions/forcenetworktype/ForceNetworkTypeFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/devoptions/forcenetworktype/ForceNetworkTypeFragmentPresenter.kt index c45d7e2d71d..5ac2b43629a 100644 --- a/app/src/main/java/org/oppia/android/app/devoptions/forcenetworktype/ForceNetworkTypeFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/devoptions/forcenetworktype/ForceNetworkTypeFragmentPresenter.kt @@ -61,7 +61,7 @@ class ForceNetworkTypeFragmentPresenter @Inject constructor( private fun createRecyclerViewAdapter(): BindableAdapter { return BindableAdapter.SingleTypeBuilder - .newBuilder() + .Factory(fragment).create() .registerViewDataBinderWithSameModelType( inflateDataBinding = ForceNetworkTypeNetworkItemViewBinding::inflate, setViewModel = this::bindNetworkItemView diff --git a/app/src/main/java/org/oppia/android/app/devoptions/markchapterscompleted/MarkChaptersCompletedFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/devoptions/markchapterscompleted/MarkChaptersCompletedFragmentPresenter.kt index 69bfbe61824..e78d3f5f409 100644 --- a/app/src/main/java/org/oppia/android/app/devoptions/markchapterscompleted/MarkChaptersCompletedFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/devoptions/markchapterscompleted/MarkChaptersCompletedFragmentPresenter.kt @@ -95,7 +95,7 @@ class MarkChaptersCompletedFragmentPresenter @Inject constructor( private fun createRecyclerViewAdapter(): BindableAdapter { return BindableAdapter.MultiTypeBuilder - .newBuilder { viewModel -> + .Factory(fragment).create { viewModel -> when (viewModel) { is StorySummaryViewModel -> ViewType.VIEW_TYPE_STORY is ChapterSummaryViewModel -> ViewType.VIEW_TYPE_CHAPTER diff --git a/app/src/main/java/org/oppia/android/app/devoptions/markstoriescompleted/MarkStoriesCompletedFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/devoptions/markstoriescompleted/MarkStoriesCompletedFragmentPresenter.kt index ccd9671bbd6..b93cd810b7b 100644 --- a/app/src/main/java/org/oppia/android/app/devoptions/markstoriescompleted/MarkStoriesCompletedFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/devoptions/markstoriescompleted/MarkStoriesCompletedFragmentPresenter.kt @@ -92,7 +92,7 @@ class MarkStoriesCompletedFragmentPresenter @Inject constructor( private fun createRecyclerViewAdapter(): BindableAdapter { return BindableAdapter.SingleTypeBuilder - .newBuilder() + .Factory(fragment).create() .registerViewDataBinderWithSameModelType( inflateDataBinding = MarkStoriesCompletedStorySummaryViewBinding::inflate, setViewModel = this::bindStorySummaryView diff --git a/app/src/main/java/org/oppia/android/app/devoptions/marktopicscompleted/MarkTopicsCompletedFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/devoptions/marktopicscompleted/MarkTopicsCompletedFragmentPresenter.kt index 3d78e8bba4d..dce2187e36f 100644 --- a/app/src/main/java/org/oppia/android/app/devoptions/marktopicscompleted/MarkTopicsCompletedFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/devoptions/marktopicscompleted/MarkTopicsCompletedFragmentPresenter.kt @@ -90,7 +90,7 @@ class MarkTopicsCompletedFragmentPresenter @Inject constructor( private fun createRecyclerViewAdapter(): BindableAdapter { return BindableAdapter.SingleTypeBuilder - .newBuilder() + .Factory(fragment).create() .registerViewDataBinderWithSameModelType( inflateDataBinding = MarkTopicsCompletedTopicViewBinding::inflate, setViewModel = this::bindTopicSummaryView diff --git a/app/src/main/java/org/oppia/android/app/devoptions/vieweventlogs/ViewEventLogsFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/devoptions/vieweventlogs/ViewEventLogsFragmentPresenter.kt index e832d184378..63fc8e5cc21 100644 --- a/app/src/main/java/org/oppia/android/app/devoptions/vieweventlogs/ViewEventLogsFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/devoptions/vieweventlogs/ViewEventLogsFragmentPresenter.kt @@ -57,7 +57,7 @@ class ViewEventLogsFragmentPresenter @Inject constructor( private fun createRecyclerViewAdapter(): BindableAdapter { return BindableAdapter.SingleTypeBuilder - .newBuilder() + .Factory(fragment).create() .registerViewDataBinderWithSameModelType( inflateDataBinding = ViewEventLogsEventLogItemViewBinding::inflate, setViewModel = ViewEventLogsEventLogItemViewBinding::setViewModel diff --git a/app/src/main/java/org/oppia/android/app/help/HelpFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/help/HelpFragmentPresenter.kt index 19c07875867..3ec43719f1a 100644 --- a/app/src/main/java/org/oppia/android/app/help/HelpFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/help/HelpFragmentPresenter.kt @@ -49,7 +49,7 @@ class HelpFragmentPresenter @Inject constructor( private fun createRecyclerViewAdapter(): BindableAdapter { return BindableAdapter.SingleTypeBuilder - .newBuilder() + .Factory(fragment).create() .registerViewDataBinderWithSameModelType( inflateDataBinding = HelpItemBinding::inflate, setViewModel = HelpItemBinding::setViewModel diff --git a/app/src/main/java/org/oppia/android/app/help/faq/FAQListFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/help/faq/FAQListFragmentPresenter.kt index 13a4f16c30f..90af115bbd2 100644 --- a/app/src/main/java/org/oppia/android/app/help/faq/FAQListFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/help/faq/FAQListFragmentPresenter.kt @@ -49,7 +49,7 @@ class FAQListFragmentPresenter @Inject constructor( private fun createRecyclerViewAdapter(): BindableAdapter { return BindableAdapter.MultiTypeBuilder - .newBuilder { viewModel -> + .Factory(fragment).create { viewModel -> when (viewModel) { is FAQHeaderViewModel -> ViewType.VIEW_TYPE_HEADER is FAQContentViewModel -> ViewType.VIEW_TYPE_CONTENT diff --git a/app/src/main/java/org/oppia/android/app/help/thirdparty/LicenseListFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/help/thirdparty/LicenseListFragmentPresenter.kt index 653c69f33f6..8b5a25e3892 100644 --- a/app/src/main/java/org/oppia/android/app/help/thirdparty/LicenseListFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/help/thirdparty/LicenseListFragmentPresenter.kt @@ -52,7 +52,7 @@ class LicenseListFragmentPresenter @Inject constructor( private fun createRecyclerViewAdapter(): BindableAdapter { return BindableAdapter.SingleTypeBuilder - .newBuilder() + .Factory(fragment).create() .registerViewDataBinderWithSameModelType( inflateDataBinding = LicenseItemBinding::inflate, setViewModel = LicenseItemBinding::setViewModel diff --git a/app/src/main/java/org/oppia/android/app/help/thirdparty/ThirdPartyDependencyListFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/help/thirdparty/ThirdPartyDependencyListFragmentPresenter.kt index c7a604729a0..4a982f3412c 100644 --- a/app/src/main/java/org/oppia/android/app/help/thirdparty/ThirdPartyDependencyListFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/help/thirdparty/ThirdPartyDependencyListFragmentPresenter.kt @@ -51,7 +51,7 @@ class ThirdPartyDependencyListFragmentPresenter @Inject constructor( private fun createRecyclerViewAdapter(): BindableAdapter { return BindableAdapter.SingleTypeBuilder - .newBuilder() + .Factory(fragment).create() .registerViewDataBinderWithSameModelType( inflateDataBinding = ThirdPartyDependencyItemBinding::inflate, setViewModel = ThirdPartyDependencyItemBinding::setViewModel diff --git a/app/src/main/java/org/oppia/android/app/hintsandsolution/HintsAndSolutionDialogFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/hintsandsolution/HintsAndSolutionDialogFragmentPresenter.kt index 50a77998d9e..a7cbba9f8fc 100644 --- a/app/src/main/java/org/oppia/android/app/hintsandsolution/HintsAndSolutionDialogFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/hintsandsolution/HintsAndSolutionDialogFragmentPresenter.kt @@ -168,7 +168,7 @@ class HintsAndSolutionDialogFragmentPresenter @Inject constructor( private fun createRecyclerViewAdapter(): BindableAdapter { return BindableAdapter.MultiTypeBuilder - .newBuilder { viewModel -> + .Factory(fragment).create { viewModel -> when (viewModel) { is HintsViewModel -> ViewType.VIEW_TYPE_HINT_ITEM is SolutionViewModel -> ViewType.VIEW_TYPE_SOLUTION_ITEM diff --git a/app/src/main/java/org/oppia/android/app/home/HomeFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/home/HomeFragmentPresenter.kt index 40df4d56fd5..38e9f7de893 100644 --- a/app/src/main/java/org/oppia/android/app/home/HomeFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/home/HomeFragmentPresenter.kt @@ -94,7 +94,7 @@ class HomeFragmentPresenter @Inject constructor( private fun createRecyclerViewAdapter(): BindableAdapter { return BindableAdapter.MultiTypeBuilder - .newBuilder { viewModel -> + .Factory(fragment).create { viewModel -> when (viewModel) { is WelcomeViewModel -> ViewType.WELCOME_MESSAGE is PromotedStoryListViewModel -> ViewType.PROMOTED_STORY_LIST diff --git a/app/src/main/java/org/oppia/android/app/home/promotedlist/ComingSoonTopicsListView.kt b/app/src/main/java/org/oppia/android/app/home/promotedlist/ComingSoonTopicsListView.kt index 3bc04b2cd25..8d35a0bc30b 100644 --- a/app/src/main/java/org/oppia/android/app/home/promotedlist/ComingSoonTopicsListView.kt +++ b/app/src/main/java/org/oppia/android/app/home/promotedlist/ComingSoonTopicsListView.kt @@ -32,6 +32,9 @@ class ComingSoonTopicsListView @JvmOverloads constructor( @Inject lateinit var oppiaLogger: OppiaLogger + @Inject + lateinit var fragment: Fragment + override fun onAttachedToWindow() { super.onAttachedToWindow() @@ -69,7 +72,7 @@ class ComingSoonTopicsListView @JvmOverloads constructor( } private fun createAdapter(): BindableAdapter { - return BindableAdapter.SingleTypeBuilder.newBuilder() + return BindableAdapter.SingleTypeBuilder.Factory(fragment).create() .registerViewBinder( inflateView = { parent -> bindingInterface.provideComingSoonTopicViewInflatedView( diff --git a/app/src/main/java/org/oppia/android/app/home/promotedlist/PromotedStoryListView.kt b/app/src/main/java/org/oppia/android/app/home/promotedlist/PromotedStoryListView.kt index 6b65159b727..5d56f9556f0 100644 --- a/app/src/main/java/org/oppia/android/app/home/promotedlist/PromotedStoryListView.kt +++ b/app/src/main/java/org/oppia/android/app/home/promotedlist/PromotedStoryListView.kt @@ -2,6 +2,7 @@ package org.oppia.android.app.home.promotedlist import android.content.Context import android.util.AttributeSet +import android.util.Log import android.view.LayoutInflater import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager @@ -32,27 +33,51 @@ class PromotedStoryListView @JvmOverloads constructor( @Inject lateinit var oppiaLogger: OppiaLogger + @Inject + lateinit var fragment: Fragment + + lateinit var promotedDataList: List + override fun onAttachedToWindow() { super.onAttachedToWindow() - val viewComponentFactory = FragmentManager.findFragment(this) as ViewComponentFactory - val viewComponent = viewComponentFactory.createViewComponent(this) as ViewComponentImpl - viewComponent.inject(this) + try { + super.onAttachedToWindow() + + val viewComponentFactory = + FragmentManager.findFragment(this) as ViewComponentFactory + val viewComponent = viewComponentFactory.createViewComponent(this) as ViewComponentImpl + viewComponent.inject(this) - // The StartSnapHelper is used to snap between items rather than smooth scrolling, so that - // the item is completely visible in [HomeFragment] as soon as learner lifts the finger - // after scrolling. - val snapHelper = StartSnapHelper() - onFlingListener = null - snapHelper.attachToRecyclerView(this) + // The StartSnapHelper is used to snap between items rather than smooth scrolling, so that + // the item is completely visible in [HomeFragment] as soon as learner lifts the finger + // after scrolling. + val snapHelper = StartSnapHelper() + onFlingListener = null + snapHelper.attachToRecyclerView(this) + + checkIfComponentsInitialized() + } catch (e: IllegalStateException) { + if (::oppiaLogger.isInitialized) + oppiaLogger.e( + "LessonThumbnailImageView", + "Throws exception on attach to window", + e + ) + } } - /** - * Sets the list of promoted stories that this view shows to the learner. - * - * @param newDataList the new list of stories to present - */ - fun setPromotedStoryList(newDataList: List?) { + private fun checkIfComponentsInitialized() { + if (::fragment.isInitialized && + ::oppiaLogger.isInitialized + ) { + bindDataToAdapter() + } else { + oppiaLogger.w(PROMOTED_STORY_LIST_VIEW_TAG, "One of components not initialized") + } + } + + private fun bindDataToAdapter() { // To reliably bind data only after the adapter is created, we manually set the data so we can first // check for the adapter; when using an existing [RecyclerViewBindingAdapter] there is no reliable // way to check that the adapter is created. @@ -61,15 +86,33 @@ class PromotedStoryListView @JvmOverloads constructor( if (adapter == null) { adapter = createAdapter() } - if (newDataList == null) { + if (::promotedDataList.isInitialized) { + (adapter as BindableAdapter<*>).setDataUnchecked(promotedDataList) + } else { oppiaLogger.w(PROMOTED_STORY_LIST_VIEW_TAG, "Failed to resolve new story list data") + } + } + + /** + * Sets the list of promoted stories that this view shows to the learner. + * + * @param newDataList the new list of stories to present + */ + fun setPromotedStoryList(newDataList: List?) { + if (newDataList != null) { + promotedDataList = newDataList + oppiaLogger.w(PROMOTED_STORY_LIST_VIEW_TAG, promotedDataList.size.toString()) } else { - (adapter as BindableAdapter<*>).setDataUnchecked(newDataList) + if (::oppiaLogger.isInitialized) { + oppiaLogger.w(PROMOTED_STORY_LIST_VIEW_TAG, "new story list data empty") + } else { + Log.e(PROMOTED_STORY_LIST_VIEW_TAG, "new story list data empty") + } } } private fun createAdapter(): BindableAdapter { - return BindableAdapter.SingleTypeBuilder.newBuilder() + return BindableAdapter.SingleTypeBuilder.Factory(fragment).create() .registerViewBinder( inflateView = { parent -> bindingInterface.providePromotedStoryCardInflatedView( diff --git a/app/src/main/java/org/oppia/android/app/onboarding/OnboardingFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/onboarding/OnboardingFragmentPresenter.kt index f359a4348c7..f6d46d97ba1 100644 --- a/app/src/main/java/org/oppia/android/app/onboarding/OnboardingFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/onboarding/OnboardingFragmentPresenter.kt @@ -96,7 +96,7 @@ class OnboardingFragmentPresenter @Inject constructor( private fun createViewPagerAdapter(): BindableAdapter { return BindableAdapter.MultiTypeBuilder - .newBuilder { viewModel -> + .Factory(fragment).create { viewModel -> when (viewModel) { is OnboardingSlideViewModel -> ViewType.ONBOARDING_MIDDLE_SLIDE is OnboardingSlideFinalViewModel -> ViewType.ONBOARDING_FINAL_SLIDE diff --git a/app/src/main/java/org/oppia/android/app/ongoingtopiclist/OngoingTopicListFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/ongoingtopiclist/OngoingTopicListFragmentPresenter.kt index dafe1d384f3..7727317cb9e 100644 --- a/app/src/main/java/org/oppia/android/app/ongoingtopiclist/OngoingTopicListFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/ongoingtopiclist/OngoingTopicListFragmentPresenter.kt @@ -57,7 +57,7 @@ class OngoingTopicListFragmentPresenter @Inject constructor( private fun createRecyclerViewAdapter(): BindableAdapter { return BindableAdapter.SingleTypeBuilder - .newBuilder() + .Factory(fragment).create() .registerViewDataBinderWithSameModelType( inflateDataBinding = OngoingTopicItemBinding::inflate, setViewModel = OngoingTopicItemBinding::setViewModel diff --git a/app/src/main/java/org/oppia/android/app/options/AppLanguageFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/options/AppLanguageFragmentPresenter.kt index 1b4b2e6a1f8..b6d0985521b 100644 --- a/app/src/main/java/org/oppia/android/app/options/AppLanguageFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/options/AppLanguageFragmentPresenter.kt @@ -42,8 +42,7 @@ class AppLanguageFragmentPresenter @Inject constructor( private fun createRecyclerViewAdapter(): BindableAdapter { return BindableAdapter.SingleTypeBuilder - .newBuilder() - .setLifecycleOwner(fragment) + .Factory(fragment).create() .registerViewDataBinderWithSameModelType( inflateDataBinding = LanguageItemsBinding::inflate, setViewModel = LanguageItemsBinding::setViewModel diff --git a/app/src/main/java/org/oppia/android/app/options/AudioLanguageFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/options/AudioLanguageFragmentPresenter.kt index 9e8cb65ee06..ccc96db1f26 100644 --- a/app/src/main/java/org/oppia/android/app/options/AudioLanguageFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/options/AudioLanguageFragmentPresenter.kt @@ -42,8 +42,7 @@ class AudioLanguageFragmentPresenter @Inject constructor( private fun createRecyclerViewAdapter(): BindableAdapter { return BindableAdapter.SingleTypeBuilder - .newBuilder() - .setLifecycleOwner(fragment) + .Factory(fragment).create() .registerViewDataBinderWithSameModelType( inflateDataBinding = LanguageItemsBinding::inflate, setViewModel = LanguageItemsBinding::setViewModel diff --git a/app/src/main/java/org/oppia/android/app/options/OptionsFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/options/OptionsFragmentPresenter.kt index 369f0efa623..e30832ffb98 100644 --- a/app/src/main/java/org/oppia/android/app/options/OptionsFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/options/OptionsFragmentPresenter.kt @@ -95,7 +95,7 @@ class OptionsFragmentPresenter @Inject constructor( isMultipane: Boolean ): BindableAdapter { return BindableAdapter.MultiTypeBuilder - .newBuilder { viewModel -> + .Factory(fragment).create { viewModel -> viewModel.isMultipane.set(isMultipane) when (viewModel) { is OptionsReadingTextSizeViewModel -> { diff --git a/app/src/main/java/org/oppia/android/app/options/ReadingTextSizeFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/options/ReadingTextSizeFragmentPresenter.kt index 79fb3be6870..9a17ab4cc9c 100644 --- a/app/src/main/java/org/oppia/android/app/options/ReadingTextSizeFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/options/ReadingTextSizeFragmentPresenter.kt @@ -49,8 +49,7 @@ class ReadingTextSizeFragmentPresenter @Inject constructor( private fun createRecyclerViewAdapter(): BindableAdapter { return BindableAdapter.SingleTypeBuilder - .newBuilder() - .setLifecycleOwner(fragment) + .Factory(fragment).create() .registerViewDataBinderWithSameModelType( inflateDataBinding = TextSizeItemsBinding::inflate, setViewModel = TextSizeItemsBinding::setViewModel diff --git a/app/src/main/java/org/oppia/android/app/player/state/DragDropSortInteractionView.kt b/app/src/main/java/org/oppia/android/app/player/state/DragDropSortInteractionView.kt index bb42f0ba1a3..eaf60347c60 100644 --- a/app/src/main/java/org/oppia/android/app/player/state/DragDropSortInteractionView.kt +++ b/app/src/main/java/org/oppia/android/app/player/state/DragDropSortInteractionView.kt @@ -53,6 +53,9 @@ class DragDropSortInteractionView @JvmOverloads constructor( @Inject lateinit var viewBindingShim: ViewBindingShim + @Inject + lateinit var fragment: Fragment + private lateinit var entityId: String private lateinit var onDragEnd: OnDragEndedListener private lateinit var onItemDrag: OnItemDragListener @@ -84,7 +87,7 @@ class DragDropSortInteractionView @JvmOverloads constructor( private fun createAdapter(): BindableAdapter { return BindableAdapter.SingleTypeBuilder - .newBuilder() + .Factory(fragment).create() .registerViewBinder( inflateView = { parent -> viewBindingShim.provideDragDropSortInteractionInflatedView( @@ -112,7 +115,7 @@ class DragDropSortInteractionView @JvmOverloads constructor( private fun createNestedAdapter(): BindableAdapter { return BindableAdapter.SingleTypeBuilder - .newBuilder() + .Factory(fragment).create() .registerViewBinder( inflateView = { parent -> viewBindingShim.provideDragDropSingleItemInflatedView( diff --git a/app/src/main/java/org/oppia/android/app/player/state/SelectionInteractionView.kt b/app/src/main/java/org/oppia/android/app/player/state/SelectionInteractionView.kt index 0a0f06df55c..3b14bf91711 100644 --- a/app/src/main/java/org/oppia/android/app/player/state/SelectionInteractionView.kt +++ b/app/src/main/java/org/oppia/android/app/player/state/SelectionInteractionView.kt @@ -45,6 +45,9 @@ class SelectionInteractionView @JvmOverloads constructor( @Inject lateinit var bindingInterface: ViewBindingShim + @Inject + lateinit var fragment: Fragment + private lateinit var entityId: String private lateinit var writtenTranslationContext: WrittenTranslationContext @@ -84,7 +87,7 @@ class SelectionInteractionView @JvmOverloads constructor( return when (selectionItemInputType) { SelectionItemInputType.CHECKBOXES -> BindableAdapter.SingleTypeBuilder - .newBuilder() + .Factory(fragment).create() .registerViewBinder( inflateView = { parent -> bindingInterface.provideSelectionInteractionViewInflatedView( @@ -108,7 +111,7 @@ class SelectionInteractionView @JvmOverloads constructor( .build() SelectionItemInputType.RADIO_BUTTONS -> BindableAdapter.SingleTypeBuilder - .newBuilder() + .Factory(fragment).create() .registerViewBinder( inflateView = { parent -> bindingInterface.provideMultipleChoiceInteractionItemsInflatedView( diff --git a/app/src/main/java/org/oppia/android/app/player/state/StatePlayerRecyclerViewAssembler.kt b/app/src/main/java/org/oppia/android/app/player/state/StatePlayerRecyclerViewAssembler.kt index 714335ff4d6..cef7c72f9f9 100644 --- a/app/src/main/java/org/oppia/android/app/player/state/StatePlayerRecyclerViewAssembler.kt +++ b/app/src/main/java/org/oppia/android/app/player/state/StatePlayerRecyclerViewAssembler.kt @@ -889,9 +889,10 @@ class StatePlayerRecyclerViewAssembler private constructor( private val resourceHandler: AppLanguageResourceHandler, private val translationController: TranslationController ) { - private val adapterBuilder = BindableAdapter.MultiTypeBuilder.newBuilder( - StateItemViewModel::viewType - ) + private val adapterBuilder = BindableAdapter.MultiTypeBuilder.Factory(fragment) + .create { + it.viewType + } /** * Tracks features individually enabled for the assembler. No features are enabled by default. @@ -1117,7 +1118,7 @@ class StatePlayerRecyclerViewAssembler private constructor( supportsConceptCards: Boolean ): BindableAdapter { return BindableAdapter.SingleTypeBuilder - .newBuilder() + .Factory(fragment).create() .registerViewBinder( inflateView = { parent -> SubmittedAnswerListItemBinding.inflate( @@ -1139,7 +1140,7 @@ class StatePlayerRecyclerViewAssembler private constructor( supportsConceptCards: Boolean ): BindableAdapter { return BindableAdapter.SingleTypeBuilder - .newBuilder() + .Factory(fragment).create() .registerViewBinder( inflateView = { parent -> SubmittedHtmlAnswerItemBinding.inflate( diff --git a/app/src/main/java/org/oppia/android/app/profile/ProfileChooserFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/profile/ProfileChooserFragmentPresenter.kt index 848440ca3a7..6f6a55e1b5d 100644 --- a/app/src/main/java/org/oppia/android/app/profile/ProfileChooserFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/profile/ProfileChooserFragmentPresenter.kt @@ -149,7 +149,7 @@ class ProfileChooserFragmentPresenter @Inject constructor( private fun createRecyclerViewAdapter(): BindableAdapter { return BindableAdapter.MultiTypeBuilder - .newBuilder( + .Factory(fragment).create( ProfileChooserUiModel::getModelTypeCase ) .registerViewDataBinderWithSameModelType( diff --git a/app/src/main/java/org/oppia/android/app/profileprogress/ProfileProgressFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/profileprogress/ProfileProgressFragmentPresenter.kt index 71f4b0451b5..f696d802ff1 100644 --- a/app/src/main/java/org/oppia/android/app/profileprogress/ProfileProgressFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/profileprogress/ProfileProgressFragmentPresenter.kt @@ -71,7 +71,7 @@ class ProfileProgressFragmentPresenter @Inject constructor( private fun createRecyclerViewAdapter(): BindableAdapter { return BindableAdapter.MultiTypeBuilder - .newBuilder { viewModel -> + .Factory(fragment).create { viewModel -> when (viewModel) { is ProfileProgressHeaderViewModel -> ViewType.VIEW_TYPE_HEADER is RecentlyPlayedStorySummaryViewModel -> ViewType.VIEW_TYPE_RECENTLY_PLAYED_STORY diff --git a/app/src/main/java/org/oppia/android/app/recyclerview/BindableAdapter.kt b/app/src/main/java/org/oppia/android/app/recyclerview/BindableAdapter.kt index 7aeebf5f67b..8cfdf2ea56f 100644 --- a/app/src/main/java/org/oppia/android/app/recyclerview/BindableAdapter.kt +++ b/app/src/main/java/org/oppia/android/app/recyclerview/BindableAdapter.kt @@ -4,11 +4,9 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.databinding.ViewDataBinding -import androidx.lifecycle.LifecycleOwner +import androidx.fragment.app.Fragment import androidx.recyclerview.widget.RecyclerView -import org.oppia.android.app.recyclerview.BindableAdapter.MultiTypeBuilder.Companion.newBuilder -import org.oppia.android.app.recyclerview.BindableAdapter.SingleTypeBuilder.Companion.newBuilder -import java.lang.ref.WeakReference +import javax.inject.Inject import kotlin.reflect.KClass /** A function that returns the integer-based type of view that can bind the specified object. */ @@ -95,63 +93,15 @@ class BindableAdapter internal constructor( internal abstract fun bind(data: T) } - /** - * The base builder for [BindableAdapter]. This class should not be used directly--use either - * [SingleTypeBuilder] or [MultiTypeBuilder] instead. - */ - abstract class BaseBuilder { - /** - * A [WeakReference] to a [LifecycleOwner] for databinding inflation. See [setLifecycleOwner]. - * Note that this needs to be a weak reference so that long-held references to the adapter do - * not potentially leak lifecycle owners (such as fragments and activities). - */ - private var lifecycleOwnerRef: WeakReference? = null - - /** - * Sets the [LifecycleOwner] corresponding to this adapter. This will automatically be used as - * the lifecycle owner for all databinding classes created during view inflation. Note that the - * adapter holds a weak reference to the owner to ensure long-lived references to the adapter - * class itself does not result in leaks, however it's up to the caller's responsibility to make - * sure that the adapter itself is not actually used after the lifecycle owner has expired - * (otherwise the app may crash). - * - * @return this - */ - fun setLifecycleOwner(lifecycleOwner: LifecycleOwner): BuilderType { - check(lifecycleOwnerRef == null) { - "A lifecycle owner has already been bound to this adapter." - } - lifecycleOwnerRef = WeakReference(lifecycleOwner) - - // This cast is not, strictly speaking, safe, but child classes are expected to inherit from - // the builder & pass their own type in. - @Suppress("UNCHECKED_CAST") return this as BuilderType - } - - /** - * Returns the [LifecycleOwner] bound to this adapter, or null if there isn't one. This method - * will throw if there was a lifecycle owner bound but is now expired. - */ - protected fun getLifecycleOwner(): LifecycleOwner? { - // Crash if the lifecycle owner has been cleaned up since it's not valid to use the adapter - // with an old lifecycle owner, and silently ignoring this may result in part of the layout - // not responding to events. - return lifecycleOwnerRef?.let { ref -> - checkNotNull(ref.get()) { - "Attempted to inflate data binding with expired lifecycle owner" - } - } - } - } - /** * Constructs a new [BindableAdapter] that for a single view type. * * Instances of [SingleTypeBuilder] should be instantiated using [newBuilder]. */ class SingleTypeBuilder( - private val dataClassType: KClass - ) : BaseBuilder>() { + private val dataClassType: KClass, + private val fragment: Fragment + ) { private lateinit var viewHolderFactory: ViewHolderFactory /** @@ -222,7 +172,7 @@ class BindableAdapter internal constructor( viewGroup, /* attachToRoot= */ false ) - binding.lifecycleOwner = getLifecycleOwner() + binding.lifecycleOwner = fragment object : BindableViewHolder(binding.root) { override fun bind(data: T) { setViewModel(binding, data) @@ -242,10 +192,11 @@ class BindableAdapter internal constructor( ) } - companion object { - /** Returns a new [SingleTypeBuilder]. */ - inline fun newBuilder(): SingleTypeBuilder { - return SingleTypeBuilder(T::class) + class Factory @Inject constructor( + val fragment: Fragment + ) { + inline fun create(): SingleTypeBuilder { + return SingleTypeBuilder(T::class, fragment) } } } @@ -258,8 +209,9 @@ class BindableAdapter internal constructor( */ class MultiTypeBuilder>( private val dataClassType: KClass, - private val computeViewType: ComputeViewType - ) : BaseBuilder>() { + private val computeViewType: ComputeViewType, + private val fragment: Fragment + ) { private var viewHolderFactoryMap: MutableMap> = HashMap() /** @@ -343,7 +295,8 @@ class BindableAdapter internal constructor( viewGroup, /* attachToRoot= */ false ) - binding.lifecycleOwner = getLifecycleOwner() + + binding.lifecycleOwner = fragment object : BindableViewHolder(binding.root) { override fun bind(data: T) { setViewModel(binding, transformViewModel(data)) @@ -371,15 +324,13 @@ class BindableAdapter internal constructor( ) } - companion object { - /** - * Returns a new [MultiTypeBuilder] with the specified function that returns the enum type of - * view a specific data item corresponds to. - */ - inline fun > newBuilder( + class Factory @Inject constructor( + val fragment: Fragment + ) { + inline fun > create( noinline computeViewType: ComputeViewType ): MultiTypeBuilder { - return MultiTypeBuilder(T::class, computeViewType) + return MultiTypeBuilder(T::class, computeViewType, fragment) } } } diff --git a/app/src/main/java/org/oppia/android/app/settings/profile/ProfileListFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/settings/profile/ProfileListFragmentPresenter.kt index fdaeec43c3b..5cd7f718e56 100644 --- a/app/src/main/java/org/oppia/android/app/settings/profile/ProfileListFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/settings/profile/ProfileListFragmentPresenter.kt @@ -53,7 +53,7 @@ class ProfileListFragmentPresenter @Inject constructor( private fun createRecyclerViewAdapter(): BindableAdapter { return BindableAdapter.SingleTypeBuilder - .newBuilder() + .Factory(fragment).create() .registerViewDataBinderWithSameModelType( inflateDataBinding = ProfileListProfileViewBinding::inflate, setViewModel = ::bindProfileView diff --git a/app/src/main/java/org/oppia/android/app/story/StoryFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/story/StoryFragmentPresenter.kt index baa1efc5b09..ce2f82c50af 100644 --- a/app/src/main/java/org/oppia/android/app/story/StoryFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/story/StoryFragmentPresenter.kt @@ -136,7 +136,7 @@ class StoryFragmentPresenter @Inject constructor( private fun createRecyclerViewAdapter(): BindableAdapter { return BindableAdapter.MultiTypeBuilder - .newBuilder { viewModel -> + .Factory(fragment).create { viewModel -> when (viewModel) { is StoryHeaderViewModel -> ViewType.VIEW_TYPE_HEADER is StoryChapterSummaryViewModel -> ViewType.VIEW_TYPE_CHAPTER diff --git a/app/src/main/java/org/oppia/android/app/testing/DragDropTestActivityPresenter.kt b/app/src/main/java/org/oppia/android/app/testing/DragDropTestActivityPresenter.kt index 592e74f62bd..631383ce55e 100644 --- a/app/src/main/java/org/oppia/android/app/testing/DragDropTestActivityPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/testing/DragDropTestActivityPresenter.kt @@ -4,6 +4,7 @@ import android.view.LayoutInflater import android.view.ViewGroup import android.widget.TextView import androidx.appcompat.app.AppCompatActivity +import androidx.fragment.app.Fragment import androidx.recyclerview.widget.RecyclerView import org.oppia.android.R import org.oppia.android.app.recyclerview.BindableAdapter @@ -24,7 +25,7 @@ class DragDropTestActivityPresenter @Inject constructor(private val activity: Ap private fun createBindableAdapter(): BindableAdapter { return BindableAdapter.SingleTypeBuilder - .newBuilder() + .Factory(Fragment()).create() .registerViewBinder( inflateView = this::inflateTextViewForStringWithoutDataBinding, bindView = this::bindTextViewForStringWithoutDataBinding diff --git a/app/src/main/java/org/oppia/android/app/topic/lessons/TopicLessonsFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/topic/lessons/TopicLessonsFragmentPresenter.kt index 5bb654e2550..82bc439ca76 100644 --- a/app/src/main/java/org/oppia/android/app/topic/lessons/TopicLessonsFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/topic/lessons/TopicLessonsFragmentPresenter.kt @@ -105,7 +105,7 @@ class TopicLessonsFragmentPresenter @Inject constructor( private fun createRecyclerViewAdapter(): BindableAdapter { return BindableAdapter.MultiTypeBuilder - .newBuilder { viewModel -> + .Factory(fragment).create { viewModel -> when (viewModel) { is StorySummaryViewModel -> ViewType.VIEW_TYPE_STORY_ITEM is TopicLessonsTitleViewModel -> ViewType.VIEW_TYPE_TITLE_TEXT @@ -208,7 +208,7 @@ class TopicLessonsFragmentPresenter @Inject constructor( private fun createChapterRecyclerViewAdapter(): BindableAdapter { return BindableAdapter.SingleTypeBuilder - .newBuilder() + .Factory(fragment).create() .registerViewDataBinderWithSameModelType( inflateDataBinding = LessonsChapterViewBinding::inflate, setViewModel = LessonsChapterViewBinding::setViewModel diff --git a/app/src/main/java/org/oppia/android/app/topic/practice/TopicPracticeFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/topic/practice/TopicPracticeFragmentPresenter.kt index edee0861394..a0b680b05be 100644 --- a/app/src/main/java/org/oppia/android/app/topic/practice/TopicPracticeFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/topic/practice/TopicPracticeFragmentPresenter.kt @@ -74,7 +74,7 @@ class TopicPracticeFragmentPresenter @Inject constructor( private fun createRecyclerViewAdapter(): BindableAdapter { return BindableAdapter.MultiTypeBuilder - .newBuilder { viewModel -> + .Factory(fragment).create { viewModel -> when (viewModel) { is TopicPracticeHeaderViewModel -> ViewType.VIEW_TYPE_HEADER is TopicPracticeSubtopicViewModel -> ViewType.VIEW_TYPE_SKILL diff --git a/app/src/main/java/org/oppia/android/app/topic/revision/TopicRevisionFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/topic/revision/TopicRevisionFragmentPresenter.kt index 91e164c271e..3ed104f392d 100755 --- a/app/src/main/java/org/oppia/android/app/topic/revision/TopicRevisionFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/topic/revision/TopicRevisionFragmentPresenter.kt @@ -71,7 +71,7 @@ class TopicRevisionFragmentPresenter @Inject constructor( private fun createRecyclerViewAdapter(): BindableAdapter { return BindableAdapter.SingleTypeBuilder - .newBuilder() + .Factory(fragment).create() .registerViewDataBinderWithSameModelType( inflateDataBinding = TopicRevisionSummaryViewBinding::inflate, setViewModel = TopicRevisionSummaryViewBinding::setViewModel diff --git a/app/src/main/java/org/oppia/android/app/walkthrough/topiclist/WalkthroughTopicListFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/walkthrough/topiclist/WalkthroughTopicListFragmentPresenter.kt index deef5d17312..d46bc65e7b8 100644 --- a/app/src/main/java/org/oppia/android/app/walkthrough/topiclist/WalkthroughTopicListFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/walkthrough/topiclist/WalkthroughTopicListFragmentPresenter.kt @@ -74,7 +74,7 @@ class WalkthroughTopicListFragmentPresenter @Inject constructor( private fun createRecyclerViewAdapter(): BindableAdapter { return BindableAdapter.MultiTypeBuilder - .newBuilder { viewModel -> + .Factory(fragment).create { viewModel -> when (viewModel) { is WalkthroughTopicHeaderViewModel -> ViewType.VIEW_TYPE_HEADER is WalkthroughTopicSummaryViewModel -> ViewType.VIEW_TYPE_TOPIC From 33dba868811d3f7d7d2c4f9ea7deb6db098446b7 Mon Sep 17 00:00:00 2001 From: kevingitonga Date: Tue, 5 Jul 2022 13:08:57 +0300 Subject: [PATCH 02/34] Fix failing tests after refactor, Create Fragment for DragDropTestActivity.kt. --- app/build.gradle | 1 + .../app/fragment/FragmentComponentImpl.kt | 2 + .../promotedlist/PromotedStoryListView.kt | 74 ++++---------- .../app/recyclerview/BindableAdapter.kt | 60 +++++++++++- .../app/testing/DragDropTestActivity.kt | 19 +--- .../testing/DragDropTestActivityPresenter.kt | 56 +++-------- .../app/testing/DragDropTestFragment.kt | 57 +++++++++++ .../testing/DragDropTestFragmentPresenter.kt | 80 ++++++++++++++++ .../res/layout/drag_drop_test_activity.xml | 10 +- .../res/layout/drag_drop_test_fragment.xml | 10 ++ .../app/recyclerview/BindableAdapterTest.kt | 96 +++++++++---------- 11 files changed, 282 insertions(+), 183 deletions(-) create mode 100644 app/src/main/java/org/oppia/android/app/testing/DragDropTestFragment.kt create mode 100644 app/src/main/java/org/oppia/android/app/testing/DragDropTestFragmentPresenter.kt create mode 100644 app/src/main/res/layout/drag_drop_test_fragment.xml diff --git a/app/build.gradle b/app/build.gradle index 3900e0b93a6..0cc6edeff46 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -206,6 +206,7 @@ dependencies { 'androidx.test.espresso:espresso-core:3.2.0', 'androidx.test.espresso:espresso-intents:3.1.0', 'androidx.test.ext:junit:1.1.1', + 'androidx.work:work-testing:2.4.0', 'com.github.bumptech.glide:mocks:4.11.0', 'com.google.truth:truth:1.1.3', 'com.google.truth.extensions:truth-liteproto-extension:1.1.3', diff --git a/app/src/main/java/org/oppia/android/app/fragment/FragmentComponentImpl.kt b/app/src/main/java/org/oppia/android/app/fragment/FragmentComponentImpl.kt index 5ec6f40e761..71688e3a4db 100644 --- a/app/src/main/java/org/oppia/android/app/fragment/FragmentComponentImpl.kt +++ b/app/src/main/java/org/oppia/android/app/fragment/FragmentComponentImpl.kt @@ -61,6 +61,7 @@ import org.oppia.android.app.settings.profile.ProfileResetPinFragment import org.oppia.android.app.shim.IntentFactoryShimModule import org.oppia.android.app.shim.ViewBindingShimModule import org.oppia.android.app.story.StoryFragment +import org.oppia.android.app.testing.DragDropTestFragment import org.oppia.android.app.testing.ExplorationTestActivityPresenter import org.oppia.android.app.testing.ImageRegionSelectionTestFragment import org.oppia.android.app.topic.TopicFragment @@ -167,4 +168,5 @@ interface FragmentComponentImpl : FragmentComponent, ViewComponentBuilderInjecto fun inject(walkthroughFinalFragment: WalkthroughFinalFragment) fun inject(walkthroughTopicListFragment: WalkthroughTopicListFragment) fun inject(walkthroughWelcomeFragment: WalkthroughWelcomeFragment) + fun inject(dragDropTestFragment: DragDropTestFragment) } diff --git a/app/src/main/java/org/oppia/android/app/home/promotedlist/PromotedStoryListView.kt b/app/src/main/java/org/oppia/android/app/home/promotedlist/PromotedStoryListView.kt index 5d56f9556f0..5869d5bc762 100644 --- a/app/src/main/java/org/oppia/android/app/home/promotedlist/PromotedStoryListView.kt +++ b/app/src/main/java/org/oppia/android/app/home/promotedlist/PromotedStoryListView.kt @@ -2,7 +2,6 @@ package org.oppia.android.app.home.promotedlist import android.content.Context import android.util.AttributeSet -import android.util.Log import android.view.LayoutInflater import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager @@ -36,48 +35,27 @@ class PromotedStoryListView @JvmOverloads constructor( @Inject lateinit var fragment: Fragment - lateinit var promotedDataList: List - override fun onAttachedToWindow() { super.onAttachedToWindow() - try { - super.onAttachedToWindow() - - val viewComponentFactory = - FragmentManager.findFragment(this) as ViewComponentFactory - val viewComponent = viewComponentFactory.createViewComponent(this) as ViewComponentImpl - viewComponent.inject(this) - - // The StartSnapHelper is used to snap between items rather than smooth scrolling, so that - // the item is completely visible in [HomeFragment] as soon as learner lifts the finger - // after scrolling. - val snapHelper = StartSnapHelper() - onFlingListener = null - snapHelper.attachToRecyclerView(this) - - checkIfComponentsInitialized() - } catch (e: IllegalStateException) { - if (::oppiaLogger.isInitialized) - oppiaLogger.e( - "LessonThumbnailImageView", - "Throws exception on attach to window", - e - ) - } - } + val viewComponentFactory = FragmentManager.findFragment(this) as ViewComponentFactory + val viewComponent = viewComponentFactory.createViewComponent(this) as ViewComponentImpl + viewComponent.inject(this) - private fun checkIfComponentsInitialized() { - if (::fragment.isInitialized && - ::oppiaLogger.isInitialized - ) { - bindDataToAdapter() - } else { - oppiaLogger.w(PROMOTED_STORY_LIST_VIEW_TAG, "One of components not initialized") - } + // The StartSnapHelper is used to snap between items rather than smooth scrolling, so that + // the item is completely visible in [HomeFragment] as soon as learner lifts the finger + // after scrolling. + val snapHelper = StartSnapHelper() + onFlingListener = null + snapHelper.attachToRecyclerView(this) } - private fun bindDataToAdapter() { + /** + * Sets the list of promoted stories that this view shows to the learner. + * + * @param newDataList the new list of stories to present + */ + fun setPromotedStoryList(newDataList: List?) { // To reliably bind data only after the adapter is created, we manually set the data so we can first // check for the adapter; when using an existing [RecyclerViewBindingAdapter] there is no reliable // way to check that the adapter is created. @@ -86,28 +64,10 @@ class PromotedStoryListView @JvmOverloads constructor( if (adapter == null) { adapter = createAdapter() } - if (::promotedDataList.isInitialized) { - (adapter as BindableAdapter<*>).setDataUnchecked(promotedDataList) - } else { + if (newDataList == null) { oppiaLogger.w(PROMOTED_STORY_LIST_VIEW_TAG, "Failed to resolve new story list data") - } - } - - /** - * Sets the list of promoted stories that this view shows to the learner. - * - * @param newDataList the new list of stories to present - */ - fun setPromotedStoryList(newDataList: List?) { - if (newDataList != null) { - promotedDataList = newDataList - oppiaLogger.w(PROMOTED_STORY_LIST_VIEW_TAG, promotedDataList.size.toString()) } else { - if (::oppiaLogger.isInitialized) { - oppiaLogger.w(PROMOTED_STORY_LIST_VIEW_TAG, "new story list data empty") - } else { - Log.e(PROMOTED_STORY_LIST_VIEW_TAG, "new story list data empty") - } + (adapter as BindableAdapter<*>).setDataUnchecked(newDataList) } } diff --git a/app/src/main/java/org/oppia/android/app/recyclerview/BindableAdapter.kt b/app/src/main/java/org/oppia/android/app/recyclerview/BindableAdapter.kt index 8cfdf2ea56f..5fa53d2d6f8 100644 --- a/app/src/main/java/org/oppia/android/app/recyclerview/BindableAdapter.kt +++ b/app/src/main/java/org/oppia/android/app/recyclerview/BindableAdapter.kt @@ -5,7 +5,9 @@ import android.view.View import android.view.ViewGroup import androidx.databinding.ViewDataBinding import androidx.fragment.app.Fragment +import androidx.lifecycle.LifecycleOwner import androidx.recyclerview.widget.RecyclerView +import java.lang.ref.WeakReference import javax.inject.Inject import kotlin.reflect.KClass @@ -93,6 +95,55 @@ class BindableAdapter internal constructor( internal abstract fun bind(data: T) } + /** + * The base builder for [BindableAdapter]. This class should not be used directly--use either + * [SingleTypeBuilder] or [MultiTypeBuilder] instead. + */ + abstract class BaseBuilder { + /** + * A [WeakReference] to a [LifecycleOwner] for databinding inflation. See [setLifecycleOwner]. + * Note that this needs to be a weak reference so that long-held references to the adapter do + * not potentially leak lifecycle owners (such as fragments and activities). + */ + private var lifecycleOwnerRef: WeakReference? = null + + /** + * Sets the [LifecycleOwner] corresponding to this adapter. This will automatically be used as + * the lifecycle owner for all databinding classes created during view inflation. Note that the + * adapter holds a weak reference to the owner to ensure long-lived references to the adapter + * class itself does not result in leaks, however it's up to the caller's responsibility to make + * sure that the adapter itself is not actually used after the lifecycle owner has expired + * (otherwise the app may crash). + * + * @return this + */ + fun setLifecycleOwner(lifecycleOwner: LifecycleOwner): BuilderType { + check(lifecycleOwnerRef == null) { + "A lifecycle owner has already been bound to this adapter." + } + lifecycleOwnerRef = WeakReference(lifecycleOwner) + + // This cast is not, strictly speaking, safe, but child classes are expected to inherit from + // the builder & pass their own type in. + @Suppress("UNCHECKED_CAST") return this as BuilderType + } + + /** + * Returns the [LifecycleOwner] bound to this adapter, or null if there isn't one. This method + * will throw if there was a lifecycle owner bound but is now expired. + */ + protected fun getLifecycleOwner(): LifecycleOwner? { + // Crash if the lifecycle owner has been cleaned up since it's not valid to use the adapter + // with an old lifecycle owner, and silently ignoring this may result in part of the layout + // not responding to events. + return lifecycleOwnerRef?.let { ref -> + checkNotNull(ref.get()) { + "Attempted to inflate data binding with expired lifecycle owner" + } + } + } + } + /** * Constructs a new [BindableAdapter] that for a single view type. * @@ -101,7 +152,7 @@ class BindableAdapter internal constructor( class SingleTypeBuilder( private val dataClassType: KClass, private val fragment: Fragment - ) { + ) : BaseBuilder>() { private lateinit var viewHolderFactory: ViewHolderFactory /** @@ -172,7 +223,7 @@ class BindableAdapter internal constructor( viewGroup, /* attachToRoot= */ false ) - binding.lifecycleOwner = fragment + binding.lifecycleOwner = getLifecycleOwner() object : BindableViewHolder(binding.root) { override fun bind(data: T) { setViewModel(binding, data) @@ -211,7 +262,7 @@ class BindableAdapter internal constructor( private val dataClassType: KClass, private val computeViewType: ComputeViewType, private val fragment: Fragment - ) { + ) : BaseBuilder>() { private var viewHolderFactoryMap: MutableMap> = HashMap() /** @@ -295,8 +346,7 @@ class BindableAdapter internal constructor( viewGroup, /* attachToRoot= */ false ) - - binding.lifecycleOwner = fragment + binding.lifecycleOwner = getLifecycleOwner() object : BindableViewHolder(binding.root) { override fun bind(data: T) { setViewModel(binding, transformViewModel(data)) diff --git a/app/src/main/java/org/oppia/android/app/testing/DragDropTestActivity.kt b/app/src/main/java/org/oppia/android/app/testing/DragDropTestActivity.kt index 4d491cf0615..95b8f7c7428 100644 --- a/app/src/main/java/org/oppia/android/app/testing/DragDropTestActivity.kt +++ b/app/src/main/java/org/oppia/android/app/testing/DragDropTestActivity.kt @@ -1,18 +1,13 @@ package org.oppia.android.app.testing import android.os.Bundle -import androidx.recyclerview.widget.RecyclerView import org.oppia.android.app.activity.ActivityComponentImpl import org.oppia.android.app.activity.InjectableAppCompatActivity -import org.oppia.android.app.recyclerview.OnDragEndedListener -import org.oppia.android.app.recyclerview.OnItemDragListener import javax.inject.Inject /** Test Activity used for testing [DragAndDropItemFacilitator] functionality */ class DragDropTestActivity : - InjectableAppCompatActivity(), - OnItemDragListener, - OnDragEndedListener { + InjectableAppCompatActivity() { @Inject lateinit var dragDropTestActivityPresenter: DragDropTestActivityPresenter @@ -22,16 +17,4 @@ class DragDropTestActivity : (activityComponent as ActivityComponentImpl).inject(this) dragDropTestActivityPresenter.handleOnCreate() } - - override fun onItemDragged( - indexFrom: Int, - indexTo: Int, - adapter: RecyclerView.Adapter - ) { - dragDropTestActivityPresenter.onItemDragged(indexFrom, indexTo, adapter) - } - - override fun onDragEnded(adapter: RecyclerView.Adapter) { - dragDropTestActivityPresenter.onDragEnded(adapter) - } } diff --git a/app/src/main/java/org/oppia/android/app/testing/DragDropTestActivityPresenter.kt b/app/src/main/java/org/oppia/android/app/testing/DragDropTestActivityPresenter.kt index 631383ce55e..6eb6833cbba 100644 --- a/app/src/main/java/org/oppia/android/app/testing/DragDropTestActivityPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/testing/DragDropTestActivityPresenter.kt @@ -1,61 +1,27 @@ package org.oppia.android.app.testing -import android.view.LayoutInflater -import android.view.ViewGroup -import android.widget.TextView import androidx.appcompat.app.AppCompatActivity -import androidx.fragment.app.Fragment -import androidx.recyclerview.widget.RecyclerView import org.oppia.android.R -import org.oppia.android.app.recyclerview.BindableAdapter import javax.inject.Inject /** The presenter for [DragDropTestActivity] */ class DragDropTestActivityPresenter @Inject constructor(private val activity: AppCompatActivity) { - var dataList = mutableListOf("Item 1", "Item 2", "Item 3", "Item 4") - fun handleOnCreate() { activity.setContentView(R.layout.drag_drop_test_activity) - activity.findViewById(R.id.drag_drop_recycler_view).apply { - adapter = createBindableAdapter() - (adapter as BindableAdapter<*>).setDataUnchecked(dataList) + if (getDragDropTestFragment() == null) { + activity.supportFragmentManager.beginTransaction().add( + R.id.drag_drop_test_fragment_placeholder, + DragDropTestFragment.newInstance() + ).commitNow() } } - private fun createBindableAdapter(): BindableAdapter { - return BindableAdapter.SingleTypeBuilder - .Factory(Fragment()).create() - .registerViewBinder( - inflateView = this::inflateTextViewForStringWithoutDataBinding, - bindView = this::bindTextViewForStringWithoutDataBinding - ) - .build() - } - - private fun bindTextViewForStringWithoutDataBinding(textView: TextView, data: String) { - textView.text = data - } - - private fun inflateTextViewForStringWithoutDataBinding(viewGroup: ViewGroup): TextView { - val inflater = LayoutInflater.from(activity) - return inflater.inflate( - R.layout.test_text_view_for_string_no_data_binding, viewGroup, /* attachToRoot= */ false - ) as TextView - } - - fun onItemDragged( - indexFrom: Int, - indexTo: Int, - adapter: RecyclerView.Adapter - ) { - val item = dataList[indexFrom] - dataList.removeAt(indexFrom) - dataList.add(indexTo, item) - adapter.notifyItemMoved(indexFrom, indexTo) - } - - fun onDragEnded(adapter: RecyclerView.Adapter) { - (adapter as BindableAdapter<*>).setDataUnchecked(dataList) + private fun getDragDropTestFragment(): DragDropTestFragment? { + return activity + .supportFragmentManager + .findFragmentById( + R.id.drag_drop_test_fragment_placeholder + ) as DragDropTestFragment? } } diff --git a/app/src/main/java/org/oppia/android/app/testing/DragDropTestFragment.kt b/app/src/main/java/org/oppia/android/app/testing/DragDropTestFragment.kt new file mode 100644 index 00000000000..6f16e0d7675 --- /dev/null +++ b/app/src/main/java/org/oppia/android/app/testing/DragDropTestFragment.kt @@ -0,0 +1,57 @@ +package org.oppia.android.app.testing + +import android.content.Context +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import org.oppia.android.app.fragment.FragmentComponentImpl +import org.oppia.android.app.fragment.InjectableFragment +import org.oppia.android.app.recyclerview.OnDragEndedListener +import org.oppia.android.app.recyclerview.OnItemDragListener +import org.oppia.android.app.story.StoryFragment +import javax.inject.Inject + +/** Fragment for displaying a DragDropTestFragment. */ +class DragDropTestFragment : InjectableFragment(), OnItemDragListener, OnDragEndedListener { + + companion object { + /** Returns a new [StoryFragment] to display the story corresponding to the specified story ID. */ + fun newInstance(): DragDropTestFragment { + return DragDropTestFragment() + } + } + + @Inject + lateinit var dragDropTestFragmentPresenter: DragDropTestFragmentPresenter + + override fun onAttach(context: Context) { + super.onAttach(context) + (fragmentComponent as FragmentComponentImpl).inject(this) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + + return dragDropTestFragmentPresenter.handleCreateView( + inflater, + container + ) + } + + override fun onDragEnded(adapter: RecyclerView.Adapter) { + dragDropTestFragmentPresenter.onDragEnded(adapter) + } + + override fun onItemDragged( + indexFrom: Int, + indexTo: Int, + adapter: RecyclerView.Adapter + ) { + dragDropTestFragmentPresenter.onItemDragged(indexFrom, indexTo, adapter) + } +} diff --git a/app/src/main/java/org/oppia/android/app/testing/DragDropTestFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/testing/DragDropTestFragmentPresenter.kt new file mode 100644 index 00000000000..6b125a2a419 --- /dev/null +++ b/app/src/main/java/org/oppia/android/app/testing/DragDropTestFragmentPresenter.kt @@ -0,0 +1,80 @@ +package org.oppia.android.app.testing + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.appcompat.app.AppCompatActivity +import androidx.fragment.app.Fragment +import androidx.recyclerview.widget.RecyclerView +import org.oppia.android.R +import org.oppia.android.app.recyclerview.BindableAdapter +import org.oppia.android.databinding.DragDropTestFragmentBinding +import org.oppia.android.domain.oppialogger.OppiaLogger +import javax.inject.Inject + +/** The presenter for [DragDropTestFragment]. */ +class DragDropTestFragmentPresenter @Inject constructor( + private val activity: AppCompatActivity, + private val fragment: Fragment, + private val oppiaLogger: OppiaLogger, +) { + + var dataList = mutableListOf("Item 1", "Item 2", "Item 3", "Item 4") + private lateinit var binding: DragDropTestFragmentBinding + + fun handleCreateView( + inflater: LayoutInflater, + container: ViewGroup? + ): View? { + + binding = DragDropTestFragmentBinding.inflate( + inflater, + container, + /* attachToRoot= */ false + ) + + binding.dragDropRecyclerView.apply { + adapter = createBindableAdapter() + (adapter as BindableAdapter<*>).setDataUnchecked(dataList) + } + + return binding.root + } + + private fun createBindableAdapter(): BindableAdapter { + return BindableAdapter.SingleTypeBuilder + .Factory(fragment).create() + .registerViewBinder( + inflateView = this::inflateTextViewForStringWithoutDataBinding, + bindView = this::bindTextViewForStringWithoutDataBinding + ) + .build() + } + + private fun bindTextViewForStringWithoutDataBinding(textView: TextView, data: String) { + textView.text = data + } + + private fun inflateTextViewForStringWithoutDataBinding(viewGroup: ViewGroup): TextView { + val inflater = LayoutInflater.from(activity) + return inflater.inflate( + R.layout.test_text_view_for_string_no_data_binding, viewGroup, /* attachToRoot= */ false + ) as TextView + } + + fun onItemDragged( + indexFrom: Int, + indexTo: Int, + adapter: RecyclerView.Adapter + ) { + val item = dataList[indexFrom] + dataList.removeAt(indexFrom) + dataList.add(indexTo, item) + adapter.notifyItemMoved(indexFrom, indexTo) + } + + fun onDragEnded(adapter: RecyclerView.Adapter) { + (adapter as BindableAdapter<*>).setDataUnchecked(dataList) + } +} diff --git a/app/src/main/res/layout/drag_drop_test_activity.xml b/app/src/main/res/layout/drag_drop_test_activity.xml index 1a2f89f87d8..73042c030a2 100644 --- a/app/src/main/res/layout/drag_drop_test_activity.xml +++ b/app/src/main/res/layout/drag_drop_test_activity.xml @@ -1,7 +1,7 @@ - + android:layout_height="match_parent" + tools:context=".app.story.StoryActivity" /> diff --git a/app/src/main/res/layout/drag_drop_test_fragment.xml b/app/src/main/res/layout/drag_drop_test_fragment.xml new file mode 100644 index 00000000000..34521233b2e --- /dev/null +++ b/app/src/main/res/layout/drag_drop_test_fragment.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/sharedTest/java/org/oppia/android/app/recyclerview/BindableAdapterTest.kt b/app/src/sharedTest/java/org/oppia/android/app/recyclerview/BindableAdapterTest.kt index a207a2e9a59..ef436215158 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/recyclerview/BindableAdapterTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/recyclerview/BindableAdapterTest.kt @@ -163,8 +163,10 @@ class BindableAdapterTest { @Test fun testSingleTypeAdapter_withOneViewType_noData_bindsNoViews() { + val testFragment = Fragment() // Set up the adapter to be used for this test. - TestModule.testAdapterFactory = { createSingleViewTypeNoDataBindingBindableAdapter() } + TestModule.testAdapterFactory = + { createSingleViewTypeNoDataBindingBindableAdapter(testFragment) } launch(BindableAdapterTestActivity::class.java).use { scenario -> scenario.onActivity { activity -> @@ -178,8 +180,10 @@ class BindableAdapterTest { @Test fun testSingleTypeAdapter_withOneViewType_setItem_automaticallyBindsView() { + val testFragment = Fragment() // Set up the adapter to be used for this test. - TestModule.testAdapterFactory = { createSingleViewTypeNoDataBindingBindableAdapter() } + TestModule.testAdapterFactory = + { createSingleViewTypeNoDataBindingBindableAdapter(testFragment) } launch(BindableAdapterTestActivity::class.java).use { scenario -> scenario.onActivity { activity -> @@ -201,8 +205,10 @@ class BindableAdapterTest { @Test fun testSingleTypeAdapter_withOneViewType_nullData_bindsNoViews() { + val testFragment = Fragment() // Set up the adapter to be used for this test. - TestModule.testAdapterFactory = { createSingleViewTypeNoDataBindingBindableAdapter() } + TestModule.testAdapterFactory = + { createSingleViewTypeNoDataBindingBindableAdapter(testFragment) } launch(BindableAdapterTestActivity::class.java).use { scenario -> scenario.onActivity { activity -> @@ -215,8 +221,10 @@ class BindableAdapterTest { @Test fun testSingleTypeAdapter_withOneViewType_setMultipleItems_automaticallyBinds() { + val testFragment = Fragment() // Set up the adapter to be used for this test. - TestModule.testAdapterFactory = { createSingleViewTypeNoDataBindingBindableAdapter() } + TestModule.testAdapterFactory = + { createSingleViewTypeNoDataBindingBindableAdapter(testFragment) } launch(BindableAdapterTestActivity::class.java).use { scenario -> scenario.onActivity { activity -> @@ -241,8 +249,10 @@ class BindableAdapterTest { @Test fun testMultiTypeAdapter_withTwoViewTypes_setItems_autoBindsCorrectItemsPerTypes() { + val testFragment = Fragment() // Set up the adapter to be used for this test. - TestModule.testAdapterFactory = { createMultiViewTypeNoDataBindingBindableAdapter() } + TestModule.testAdapterFactory = + { createMultiViewTypeNoDataBindingBindableAdapter(testFragment) } launch(BindableAdapterTestActivity::class.java).use { scenario -> scenario.onActivity { activity -> @@ -277,8 +287,10 @@ class BindableAdapterTest { @Test fun testSingleTypeAdapter_withOneDataBoundViewType_setItem_automaticallyBindsView() { + val testFragment = Fragment() // Set up the adapter to be used for this test. - TestModule.testAdapterFactory = { createSingleViewTypeWithDataBindingBindableAdapter() } + TestModule.testAdapterFactory = + { createSingleViewTypeWithDataBindingBindableAdapter(testFragment) } launch(BindableAdapterTestActivity::class.java).use { scenario -> scenario.onActivity { activity -> @@ -300,8 +312,13 @@ class BindableAdapterTest { @Test fun testSingleTypeAdapter_withTwoDataBoundViewTypes_setItems_autoBindsCorrectItemsPerTypes() { + val testFragment = Fragment() // Set up the adapter to be used for this test. - TestModule.testAdapterFactory = { createSingleViewTypeWithDataBindingBindableAdapter() } + TestModule.testAdapterFactory = { + createSingleViewTypeWithDataBindingBindableAdapter( + testFragment + ) + } launch(BindableAdapterTestActivity::class.java).use { scenario -> scenario.onActivity { activity -> @@ -327,8 +344,10 @@ class BindableAdapterTest { @Test fun testMultiTypeAdapter_partiallyDataBoundViewTypes_setItems_autoBindsCorrectItemsPerTypes() { + val testFragment = Fragment() // Set up the adapter to be used for this test. - TestModule.testAdapterFactory = { createMultiViewTypeWithDataBindingBindableAdapter() } + TestModule.testAdapterFactory = + { createMultiViewTypeWithDataBindingBindableAdapter(testFragment) } launch(BindableAdapterTestActivity::class.java).use { scenario -> scenario.onActivity { activity -> @@ -367,7 +386,7 @@ class BindableAdapterTest { val exception = assertThrows(IllegalStateException::class) { SingleTypeBuilder - .newBuilder() + .Factory(testFragment).create() .setLifecycleOwner(testFragment) .setLifecycleOwner(testFragment) .build() @@ -381,7 +400,7 @@ class BindableAdapterTest { val exception = assertThrows(IllegalStateException::class) { MultiTypeBuilder - .newBuilder(ViewModelType.Companion::deriveTypeFrom) + .Factory(testFragment).create(ViewModelType.Companion::deriveTypeFrom) .setLifecycleOwner(testFragment) .setLifecycleOwner(testFragment) .build() @@ -391,8 +410,10 @@ class BindableAdapterTest { @Test fun testSingleTypeAdapter_withLiveData_noLifecycleOwner_doesNotRebindLiveDataValues() { + val testFragment = Fragment() // Set up the adapter to be used for this test. - TestModule.testAdapterFactory = { createSingleViewTypeWithDataBindingAndLiveDataAdapter() } + TestModule.testAdapterFactory = + { createSingleViewTypeWithDataBindingAndLiveDataAdapter(testFragment) } launch(BindableAdapterTestActivity::class.java).use { scenario -> val itemLiveData = MutableLiveData("initial") @@ -444,8 +465,10 @@ class BindableAdapterTest { @Test fun testMultiTypeAdapter_withLiveData_noLifecycleOwner_doesNotRebindLiveDataValues() { + val testFragment = Fragment() // Set up the adapter to be used for this test. - TestModule.testAdapterFactory = { createMultiViewTypeWithDataBindingBindableAdapter() } + TestModule.testAdapterFactory = + { createMultiViewTypeWithDataBindingBindableAdapter(testFragment) } launch(BindableAdapterTestActivity::class.java).use { scenario -> val itemLiveData = MutableLiveData("initial") @@ -499,10 +522,10 @@ class BindableAdapterTest { ApplicationProvider.getApplicationContext().inject(this) } - private fun createSingleViewTypeNoDataBindingBindableAdapter(): + private fun createSingleViewTypeNoDataBindingBindableAdapter(testFragment: Fragment): BindableAdapter { return SingleTypeBuilder - .newBuilder() + .Factory(testFragment).create() .registerViewBinder( inflateView = this::inflateTextViewForStringWithoutDataBinding, bindView = this::bindTextViewForStringWithoutDataBinding @@ -510,10 +533,10 @@ class BindableAdapterTest { .build() } - private fun createSingleViewTypeWithDataBindingBindableAdapter(): + private fun createSingleViewTypeWithDataBindingBindableAdapter(testFragment: Fragment): BindableAdapter { return SingleTypeBuilder - .newBuilder() + .Factory(testFragment).create() .registerViewDataBinderWithSameModelType( inflateDataBinding = TestTextViewForStringWithDataBindingBinding::inflate, setViewModel = TestTextViewForStringWithDataBindingBinding::setViewModel @@ -521,22 +544,11 @@ class BindableAdapterTest { .build() } - private fun createSingleViewTypeWithDataBindingAndLiveDataAdapter(): - BindableAdapter { - return SingleTypeBuilder - .newBuilder() - .registerViewDataBinderWithSameModelType( - inflateDataBinding = TestTextViewForLiveDataWithDataBindingBinding::inflate, - setViewModel = TestTextViewForLiveDataWithDataBindingBinding::setViewModel - ) - .build() - } - private fun createSingleViewTypeWithDataBindingAndLiveDataAdapter( lifecycleOwner: Fragment ): BindableAdapter { return SingleTypeBuilder - .newBuilder() + .Factory(lifecycleOwner).create() .setLifecycleOwner(lifecycleOwner) .registerViewDataBinderWithSameModelType( inflateDataBinding = TestTextViewForLiveDataWithDataBindingBinding::inflate, @@ -545,10 +557,10 @@ class BindableAdapterTest { .build() } - private fun createMultiViewTypeNoDataBindingBindableAdapter(): + private fun createMultiViewTypeNoDataBindingBindableAdapter(testFragment: Fragment): BindableAdapter { return MultiTypeBuilder - .newBuilder(ViewModelType.Companion::deriveTypeFrom) + .Factory(testFragment).create(ViewModelType.Companion::deriveTypeFrom) .registerViewBinder( viewType = ViewModelType.STRING, inflateView = this::inflateTextViewForStringWithoutDataBinding, @@ -562,33 +574,11 @@ class BindableAdapterTest { .build() } - private fun createMultiViewTypeWithDataBindingBindableAdapter(): - BindableAdapter { - return MultiTypeBuilder - .newBuilder(ViewModelType.Companion::deriveTypeFrom) - .registerViewDataBinderWithSameModelType( - viewType = ViewModelType.STRING, - inflateDataBinding = TestTextViewForStringWithDataBindingBinding::inflate, - setViewModel = TestTextViewForStringWithDataBindingBinding::setViewModel - ) - .registerViewDataBinderWithSameModelType( - viewType = ViewModelType.INT, - inflateDataBinding = TestTextViewForIntWithDataBindingBinding::inflate, - setViewModel = TestTextViewForIntWithDataBindingBinding::setViewModel - ) - .registerViewDataBinderWithSameModelType( - viewType = ViewModelType.LIVE_DATA, - inflateDataBinding = TestTextViewForLiveDataWithDataBindingBinding::inflate, - setViewModel = TestTextViewForLiveDataWithDataBindingBinding::setViewModel - ) - .build() - } - private fun createMultiViewTypeWithDataBindingBindableAdapter( lifecycleOwner: Fragment ): BindableAdapter { return MultiTypeBuilder - .newBuilder(ViewModelType.Companion::deriveTypeFrom) + .Factory(lifecycleOwner).create(ViewModelType.Companion::deriveTypeFrom) .setLifecycleOwner(lifecycleOwner) .registerViewDataBinderWithSameModelType( viewType = ViewModelType.STRING, From 0a1a5b30fe91078813bac9f81c3ae6e758168b8c Mon Sep 17 00:00:00 2001 From: kevingitonga Date: Tue, 5 Jul 2022 17:01:34 +0300 Subject: [PATCH 03/34] Inject the BindableAdapter.kt factory into the fragment and listview instances. --- app/build.gradle | 1 - .../app/fragment/FragmentComponentImpl.kt | 2 - .../app/recyclerview/BindableAdapter.kt | 60 +----------- .../app/testing/DragDropTestActivity.kt | 19 +++- .../testing/DragDropTestActivityPresenter.kt | 56 ++++++++--- .../app/testing/DragDropTestFragment.kt | 57 ----------- .../testing/DragDropTestFragmentPresenter.kt | 80 ---------------- .../res/layout/drag_drop_test_activity.xml | 10 +- .../res/layout/drag_drop_test_fragment.xml | 10 -- .../app/recyclerview/BindableAdapterTest.kt | 96 ++++++++++--------- 10 files changed, 126 insertions(+), 265 deletions(-) delete mode 100644 app/src/main/java/org/oppia/android/app/testing/DragDropTestFragment.kt delete mode 100644 app/src/main/java/org/oppia/android/app/testing/DragDropTestFragmentPresenter.kt delete mode 100644 app/src/main/res/layout/drag_drop_test_fragment.xml diff --git a/app/build.gradle b/app/build.gradle index 0cc6edeff46..3900e0b93a6 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -206,7 +206,6 @@ dependencies { 'androidx.test.espresso:espresso-core:3.2.0', 'androidx.test.espresso:espresso-intents:3.1.0', 'androidx.test.ext:junit:1.1.1', - 'androidx.work:work-testing:2.4.0', 'com.github.bumptech.glide:mocks:4.11.0', 'com.google.truth:truth:1.1.3', 'com.google.truth.extensions:truth-liteproto-extension:1.1.3', diff --git a/app/src/main/java/org/oppia/android/app/fragment/FragmentComponentImpl.kt b/app/src/main/java/org/oppia/android/app/fragment/FragmentComponentImpl.kt index 71688e3a4db..5ec6f40e761 100644 --- a/app/src/main/java/org/oppia/android/app/fragment/FragmentComponentImpl.kt +++ b/app/src/main/java/org/oppia/android/app/fragment/FragmentComponentImpl.kt @@ -61,7 +61,6 @@ import org.oppia.android.app.settings.profile.ProfileResetPinFragment import org.oppia.android.app.shim.IntentFactoryShimModule import org.oppia.android.app.shim.ViewBindingShimModule import org.oppia.android.app.story.StoryFragment -import org.oppia.android.app.testing.DragDropTestFragment import org.oppia.android.app.testing.ExplorationTestActivityPresenter import org.oppia.android.app.testing.ImageRegionSelectionTestFragment import org.oppia.android.app.topic.TopicFragment @@ -168,5 +167,4 @@ interface FragmentComponentImpl : FragmentComponent, ViewComponentBuilderInjecto fun inject(walkthroughFinalFragment: WalkthroughFinalFragment) fun inject(walkthroughTopicListFragment: WalkthroughTopicListFragment) fun inject(walkthroughWelcomeFragment: WalkthroughWelcomeFragment) - fun inject(dragDropTestFragment: DragDropTestFragment) } diff --git a/app/src/main/java/org/oppia/android/app/recyclerview/BindableAdapter.kt b/app/src/main/java/org/oppia/android/app/recyclerview/BindableAdapter.kt index 5fa53d2d6f8..8cfdf2ea56f 100644 --- a/app/src/main/java/org/oppia/android/app/recyclerview/BindableAdapter.kt +++ b/app/src/main/java/org/oppia/android/app/recyclerview/BindableAdapter.kt @@ -5,9 +5,7 @@ import android.view.View import android.view.ViewGroup import androidx.databinding.ViewDataBinding import androidx.fragment.app.Fragment -import androidx.lifecycle.LifecycleOwner import androidx.recyclerview.widget.RecyclerView -import java.lang.ref.WeakReference import javax.inject.Inject import kotlin.reflect.KClass @@ -95,55 +93,6 @@ class BindableAdapter internal constructor( internal abstract fun bind(data: T) } - /** - * The base builder for [BindableAdapter]. This class should not be used directly--use either - * [SingleTypeBuilder] or [MultiTypeBuilder] instead. - */ - abstract class BaseBuilder { - /** - * A [WeakReference] to a [LifecycleOwner] for databinding inflation. See [setLifecycleOwner]. - * Note that this needs to be a weak reference so that long-held references to the adapter do - * not potentially leak lifecycle owners (such as fragments and activities). - */ - private var lifecycleOwnerRef: WeakReference? = null - - /** - * Sets the [LifecycleOwner] corresponding to this adapter. This will automatically be used as - * the lifecycle owner for all databinding classes created during view inflation. Note that the - * adapter holds a weak reference to the owner to ensure long-lived references to the adapter - * class itself does not result in leaks, however it's up to the caller's responsibility to make - * sure that the adapter itself is not actually used after the lifecycle owner has expired - * (otherwise the app may crash). - * - * @return this - */ - fun setLifecycleOwner(lifecycleOwner: LifecycleOwner): BuilderType { - check(lifecycleOwnerRef == null) { - "A lifecycle owner has already been bound to this adapter." - } - lifecycleOwnerRef = WeakReference(lifecycleOwner) - - // This cast is not, strictly speaking, safe, but child classes are expected to inherit from - // the builder & pass their own type in. - @Suppress("UNCHECKED_CAST") return this as BuilderType - } - - /** - * Returns the [LifecycleOwner] bound to this adapter, or null if there isn't one. This method - * will throw if there was a lifecycle owner bound but is now expired. - */ - protected fun getLifecycleOwner(): LifecycleOwner? { - // Crash if the lifecycle owner has been cleaned up since it's not valid to use the adapter - // with an old lifecycle owner, and silently ignoring this may result in part of the layout - // not responding to events. - return lifecycleOwnerRef?.let { ref -> - checkNotNull(ref.get()) { - "Attempted to inflate data binding with expired lifecycle owner" - } - } - } - } - /** * Constructs a new [BindableAdapter] that for a single view type. * @@ -152,7 +101,7 @@ class BindableAdapter internal constructor( class SingleTypeBuilder( private val dataClassType: KClass, private val fragment: Fragment - ) : BaseBuilder>() { + ) { private lateinit var viewHolderFactory: ViewHolderFactory /** @@ -223,7 +172,7 @@ class BindableAdapter internal constructor( viewGroup, /* attachToRoot= */ false ) - binding.lifecycleOwner = getLifecycleOwner() + binding.lifecycleOwner = fragment object : BindableViewHolder(binding.root) { override fun bind(data: T) { setViewModel(binding, data) @@ -262,7 +211,7 @@ class BindableAdapter internal constructor( private val dataClassType: KClass, private val computeViewType: ComputeViewType, private val fragment: Fragment - ) : BaseBuilder>() { + ) { private var viewHolderFactoryMap: MutableMap> = HashMap() /** @@ -346,7 +295,8 @@ class BindableAdapter internal constructor( viewGroup, /* attachToRoot= */ false ) - binding.lifecycleOwner = getLifecycleOwner() + + binding.lifecycleOwner = fragment object : BindableViewHolder(binding.root) { override fun bind(data: T) { setViewModel(binding, transformViewModel(data)) diff --git a/app/src/main/java/org/oppia/android/app/testing/DragDropTestActivity.kt b/app/src/main/java/org/oppia/android/app/testing/DragDropTestActivity.kt index 95b8f7c7428..4d491cf0615 100644 --- a/app/src/main/java/org/oppia/android/app/testing/DragDropTestActivity.kt +++ b/app/src/main/java/org/oppia/android/app/testing/DragDropTestActivity.kt @@ -1,13 +1,18 @@ package org.oppia.android.app.testing import android.os.Bundle +import androidx.recyclerview.widget.RecyclerView import org.oppia.android.app.activity.ActivityComponentImpl import org.oppia.android.app.activity.InjectableAppCompatActivity +import org.oppia.android.app.recyclerview.OnDragEndedListener +import org.oppia.android.app.recyclerview.OnItemDragListener import javax.inject.Inject /** Test Activity used for testing [DragAndDropItemFacilitator] functionality */ class DragDropTestActivity : - InjectableAppCompatActivity() { + InjectableAppCompatActivity(), + OnItemDragListener, + OnDragEndedListener { @Inject lateinit var dragDropTestActivityPresenter: DragDropTestActivityPresenter @@ -17,4 +22,16 @@ class DragDropTestActivity : (activityComponent as ActivityComponentImpl).inject(this) dragDropTestActivityPresenter.handleOnCreate() } + + override fun onItemDragged( + indexFrom: Int, + indexTo: Int, + adapter: RecyclerView.Adapter + ) { + dragDropTestActivityPresenter.onItemDragged(indexFrom, indexTo, adapter) + } + + override fun onDragEnded(adapter: RecyclerView.Adapter) { + dragDropTestActivityPresenter.onDragEnded(adapter) + } } diff --git a/app/src/main/java/org/oppia/android/app/testing/DragDropTestActivityPresenter.kt b/app/src/main/java/org/oppia/android/app/testing/DragDropTestActivityPresenter.kt index 6eb6833cbba..631383ce55e 100644 --- a/app/src/main/java/org/oppia/android/app/testing/DragDropTestActivityPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/testing/DragDropTestActivityPresenter.kt @@ -1,27 +1,61 @@ package org.oppia.android.app.testing +import android.view.LayoutInflater +import android.view.ViewGroup +import android.widget.TextView import androidx.appcompat.app.AppCompatActivity +import androidx.fragment.app.Fragment +import androidx.recyclerview.widget.RecyclerView import org.oppia.android.R +import org.oppia.android.app.recyclerview.BindableAdapter import javax.inject.Inject /** The presenter for [DragDropTestActivity] */ class DragDropTestActivityPresenter @Inject constructor(private val activity: AppCompatActivity) { + var dataList = mutableListOf("Item 1", "Item 2", "Item 3", "Item 4") + fun handleOnCreate() { activity.setContentView(R.layout.drag_drop_test_activity) - if (getDragDropTestFragment() == null) { - activity.supportFragmentManager.beginTransaction().add( - R.id.drag_drop_test_fragment_placeholder, - DragDropTestFragment.newInstance() - ).commitNow() + activity.findViewById(R.id.drag_drop_recycler_view).apply { + adapter = createBindableAdapter() + (adapter as BindableAdapter<*>).setDataUnchecked(dataList) } } - private fun getDragDropTestFragment(): DragDropTestFragment? { - return activity - .supportFragmentManager - .findFragmentById( - R.id.drag_drop_test_fragment_placeholder - ) as DragDropTestFragment? + private fun createBindableAdapter(): BindableAdapter { + return BindableAdapter.SingleTypeBuilder + .Factory(Fragment()).create() + .registerViewBinder( + inflateView = this::inflateTextViewForStringWithoutDataBinding, + bindView = this::bindTextViewForStringWithoutDataBinding + ) + .build() + } + + private fun bindTextViewForStringWithoutDataBinding(textView: TextView, data: String) { + textView.text = data + } + + private fun inflateTextViewForStringWithoutDataBinding(viewGroup: ViewGroup): TextView { + val inflater = LayoutInflater.from(activity) + return inflater.inflate( + R.layout.test_text_view_for_string_no_data_binding, viewGroup, /* attachToRoot= */ false + ) as TextView + } + + fun onItemDragged( + indexFrom: Int, + indexTo: Int, + adapter: RecyclerView.Adapter + ) { + val item = dataList[indexFrom] + dataList.removeAt(indexFrom) + dataList.add(indexTo, item) + adapter.notifyItemMoved(indexFrom, indexTo) + } + + fun onDragEnded(adapter: RecyclerView.Adapter) { + (adapter as BindableAdapter<*>).setDataUnchecked(dataList) } } diff --git a/app/src/main/java/org/oppia/android/app/testing/DragDropTestFragment.kt b/app/src/main/java/org/oppia/android/app/testing/DragDropTestFragment.kt deleted file mode 100644 index 6f16e0d7675..00000000000 --- a/app/src/main/java/org/oppia/android/app/testing/DragDropTestFragment.kt +++ /dev/null @@ -1,57 +0,0 @@ -package org.oppia.android.app.testing - -import android.content.Context -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.recyclerview.widget.RecyclerView -import org.oppia.android.app.fragment.FragmentComponentImpl -import org.oppia.android.app.fragment.InjectableFragment -import org.oppia.android.app.recyclerview.OnDragEndedListener -import org.oppia.android.app.recyclerview.OnItemDragListener -import org.oppia.android.app.story.StoryFragment -import javax.inject.Inject - -/** Fragment for displaying a DragDropTestFragment. */ -class DragDropTestFragment : InjectableFragment(), OnItemDragListener, OnDragEndedListener { - - companion object { - /** Returns a new [StoryFragment] to display the story corresponding to the specified story ID. */ - fun newInstance(): DragDropTestFragment { - return DragDropTestFragment() - } - } - - @Inject - lateinit var dragDropTestFragmentPresenter: DragDropTestFragmentPresenter - - override fun onAttach(context: Context) { - super.onAttach(context) - (fragmentComponent as FragmentComponentImpl).inject(this) - } - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - - return dragDropTestFragmentPresenter.handleCreateView( - inflater, - container - ) - } - - override fun onDragEnded(adapter: RecyclerView.Adapter) { - dragDropTestFragmentPresenter.onDragEnded(adapter) - } - - override fun onItemDragged( - indexFrom: Int, - indexTo: Int, - adapter: RecyclerView.Adapter - ) { - dragDropTestFragmentPresenter.onItemDragged(indexFrom, indexTo, adapter) - } -} diff --git a/app/src/main/java/org/oppia/android/app/testing/DragDropTestFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/testing/DragDropTestFragmentPresenter.kt deleted file mode 100644 index 6b125a2a419..00000000000 --- a/app/src/main/java/org/oppia/android/app/testing/DragDropTestFragmentPresenter.kt +++ /dev/null @@ -1,80 +0,0 @@ -package org.oppia.android.app.testing - -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.TextView -import androidx.appcompat.app.AppCompatActivity -import androidx.fragment.app.Fragment -import androidx.recyclerview.widget.RecyclerView -import org.oppia.android.R -import org.oppia.android.app.recyclerview.BindableAdapter -import org.oppia.android.databinding.DragDropTestFragmentBinding -import org.oppia.android.domain.oppialogger.OppiaLogger -import javax.inject.Inject - -/** The presenter for [DragDropTestFragment]. */ -class DragDropTestFragmentPresenter @Inject constructor( - private val activity: AppCompatActivity, - private val fragment: Fragment, - private val oppiaLogger: OppiaLogger, -) { - - var dataList = mutableListOf("Item 1", "Item 2", "Item 3", "Item 4") - private lateinit var binding: DragDropTestFragmentBinding - - fun handleCreateView( - inflater: LayoutInflater, - container: ViewGroup? - ): View? { - - binding = DragDropTestFragmentBinding.inflate( - inflater, - container, - /* attachToRoot= */ false - ) - - binding.dragDropRecyclerView.apply { - adapter = createBindableAdapter() - (adapter as BindableAdapter<*>).setDataUnchecked(dataList) - } - - return binding.root - } - - private fun createBindableAdapter(): BindableAdapter { - return BindableAdapter.SingleTypeBuilder - .Factory(fragment).create() - .registerViewBinder( - inflateView = this::inflateTextViewForStringWithoutDataBinding, - bindView = this::bindTextViewForStringWithoutDataBinding - ) - .build() - } - - private fun bindTextViewForStringWithoutDataBinding(textView: TextView, data: String) { - textView.text = data - } - - private fun inflateTextViewForStringWithoutDataBinding(viewGroup: ViewGroup): TextView { - val inflater = LayoutInflater.from(activity) - return inflater.inflate( - R.layout.test_text_view_for_string_no_data_binding, viewGroup, /* attachToRoot= */ false - ) as TextView - } - - fun onItemDragged( - indexFrom: Int, - indexTo: Int, - adapter: RecyclerView.Adapter - ) { - val item = dataList[indexFrom] - dataList.removeAt(indexFrom) - dataList.add(indexTo, item) - adapter.notifyItemMoved(indexFrom, indexTo) - } - - fun onDragEnded(adapter: RecyclerView.Adapter) { - (adapter as BindableAdapter<*>).setDataUnchecked(dataList) - } -} diff --git a/app/src/main/res/layout/drag_drop_test_activity.xml b/app/src/main/res/layout/drag_drop_test_activity.xml index 73042c030a2..1a2f89f87d8 100644 --- a/app/src/main/res/layout/drag_drop_test_activity.xml +++ b/app/src/main/res/layout/drag_drop_test_activity.xml @@ -1,7 +1,7 @@ - + android:layout_height="wrap_content" + app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" /> diff --git a/app/src/main/res/layout/drag_drop_test_fragment.xml b/app/src/main/res/layout/drag_drop_test_fragment.xml deleted file mode 100644 index 34521233b2e..00000000000 --- a/app/src/main/res/layout/drag_drop_test_fragment.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/src/sharedTest/java/org/oppia/android/app/recyclerview/BindableAdapterTest.kt b/app/src/sharedTest/java/org/oppia/android/app/recyclerview/BindableAdapterTest.kt index ef436215158..a207a2e9a59 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/recyclerview/BindableAdapterTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/recyclerview/BindableAdapterTest.kt @@ -163,10 +163,8 @@ class BindableAdapterTest { @Test fun testSingleTypeAdapter_withOneViewType_noData_bindsNoViews() { - val testFragment = Fragment() // Set up the adapter to be used for this test. - TestModule.testAdapterFactory = - { createSingleViewTypeNoDataBindingBindableAdapter(testFragment) } + TestModule.testAdapterFactory = { createSingleViewTypeNoDataBindingBindableAdapter() } launch(BindableAdapterTestActivity::class.java).use { scenario -> scenario.onActivity { activity -> @@ -180,10 +178,8 @@ class BindableAdapterTest { @Test fun testSingleTypeAdapter_withOneViewType_setItem_automaticallyBindsView() { - val testFragment = Fragment() // Set up the adapter to be used for this test. - TestModule.testAdapterFactory = - { createSingleViewTypeNoDataBindingBindableAdapter(testFragment) } + TestModule.testAdapterFactory = { createSingleViewTypeNoDataBindingBindableAdapter() } launch(BindableAdapterTestActivity::class.java).use { scenario -> scenario.onActivity { activity -> @@ -205,10 +201,8 @@ class BindableAdapterTest { @Test fun testSingleTypeAdapter_withOneViewType_nullData_bindsNoViews() { - val testFragment = Fragment() // Set up the adapter to be used for this test. - TestModule.testAdapterFactory = - { createSingleViewTypeNoDataBindingBindableAdapter(testFragment) } + TestModule.testAdapterFactory = { createSingleViewTypeNoDataBindingBindableAdapter() } launch(BindableAdapterTestActivity::class.java).use { scenario -> scenario.onActivity { activity -> @@ -221,10 +215,8 @@ class BindableAdapterTest { @Test fun testSingleTypeAdapter_withOneViewType_setMultipleItems_automaticallyBinds() { - val testFragment = Fragment() // Set up the adapter to be used for this test. - TestModule.testAdapterFactory = - { createSingleViewTypeNoDataBindingBindableAdapter(testFragment) } + TestModule.testAdapterFactory = { createSingleViewTypeNoDataBindingBindableAdapter() } launch(BindableAdapterTestActivity::class.java).use { scenario -> scenario.onActivity { activity -> @@ -249,10 +241,8 @@ class BindableAdapterTest { @Test fun testMultiTypeAdapter_withTwoViewTypes_setItems_autoBindsCorrectItemsPerTypes() { - val testFragment = Fragment() // Set up the adapter to be used for this test. - TestModule.testAdapterFactory = - { createMultiViewTypeNoDataBindingBindableAdapter(testFragment) } + TestModule.testAdapterFactory = { createMultiViewTypeNoDataBindingBindableAdapter() } launch(BindableAdapterTestActivity::class.java).use { scenario -> scenario.onActivity { activity -> @@ -287,10 +277,8 @@ class BindableAdapterTest { @Test fun testSingleTypeAdapter_withOneDataBoundViewType_setItem_automaticallyBindsView() { - val testFragment = Fragment() // Set up the adapter to be used for this test. - TestModule.testAdapterFactory = - { createSingleViewTypeWithDataBindingBindableAdapter(testFragment) } + TestModule.testAdapterFactory = { createSingleViewTypeWithDataBindingBindableAdapter() } launch(BindableAdapterTestActivity::class.java).use { scenario -> scenario.onActivity { activity -> @@ -312,13 +300,8 @@ class BindableAdapterTest { @Test fun testSingleTypeAdapter_withTwoDataBoundViewTypes_setItems_autoBindsCorrectItemsPerTypes() { - val testFragment = Fragment() // Set up the adapter to be used for this test. - TestModule.testAdapterFactory = { - createSingleViewTypeWithDataBindingBindableAdapter( - testFragment - ) - } + TestModule.testAdapterFactory = { createSingleViewTypeWithDataBindingBindableAdapter() } launch(BindableAdapterTestActivity::class.java).use { scenario -> scenario.onActivity { activity -> @@ -344,10 +327,8 @@ class BindableAdapterTest { @Test fun testMultiTypeAdapter_partiallyDataBoundViewTypes_setItems_autoBindsCorrectItemsPerTypes() { - val testFragment = Fragment() // Set up the adapter to be used for this test. - TestModule.testAdapterFactory = - { createMultiViewTypeWithDataBindingBindableAdapter(testFragment) } + TestModule.testAdapterFactory = { createMultiViewTypeWithDataBindingBindableAdapter() } launch(BindableAdapterTestActivity::class.java).use { scenario -> scenario.onActivity { activity -> @@ -386,7 +367,7 @@ class BindableAdapterTest { val exception = assertThrows(IllegalStateException::class) { SingleTypeBuilder - .Factory(testFragment).create() + .newBuilder() .setLifecycleOwner(testFragment) .setLifecycleOwner(testFragment) .build() @@ -400,7 +381,7 @@ class BindableAdapterTest { val exception = assertThrows(IllegalStateException::class) { MultiTypeBuilder - .Factory(testFragment).create(ViewModelType.Companion::deriveTypeFrom) + .newBuilder(ViewModelType.Companion::deriveTypeFrom) .setLifecycleOwner(testFragment) .setLifecycleOwner(testFragment) .build() @@ -410,10 +391,8 @@ class BindableAdapterTest { @Test fun testSingleTypeAdapter_withLiveData_noLifecycleOwner_doesNotRebindLiveDataValues() { - val testFragment = Fragment() // Set up the adapter to be used for this test. - TestModule.testAdapterFactory = - { createSingleViewTypeWithDataBindingAndLiveDataAdapter(testFragment) } + TestModule.testAdapterFactory = { createSingleViewTypeWithDataBindingAndLiveDataAdapter() } launch(BindableAdapterTestActivity::class.java).use { scenario -> val itemLiveData = MutableLiveData("initial") @@ -465,10 +444,8 @@ class BindableAdapterTest { @Test fun testMultiTypeAdapter_withLiveData_noLifecycleOwner_doesNotRebindLiveDataValues() { - val testFragment = Fragment() // Set up the adapter to be used for this test. - TestModule.testAdapterFactory = - { createMultiViewTypeWithDataBindingBindableAdapter(testFragment) } + TestModule.testAdapterFactory = { createMultiViewTypeWithDataBindingBindableAdapter() } launch(BindableAdapterTestActivity::class.java).use { scenario -> val itemLiveData = MutableLiveData("initial") @@ -522,10 +499,10 @@ class BindableAdapterTest { ApplicationProvider.getApplicationContext().inject(this) } - private fun createSingleViewTypeNoDataBindingBindableAdapter(testFragment: Fragment): + private fun createSingleViewTypeNoDataBindingBindableAdapter(): BindableAdapter { return SingleTypeBuilder - .Factory(testFragment).create() + .newBuilder() .registerViewBinder( inflateView = this::inflateTextViewForStringWithoutDataBinding, bindView = this::bindTextViewForStringWithoutDataBinding @@ -533,10 +510,10 @@ class BindableAdapterTest { .build() } - private fun createSingleViewTypeWithDataBindingBindableAdapter(testFragment: Fragment): + private fun createSingleViewTypeWithDataBindingBindableAdapter(): BindableAdapter { return SingleTypeBuilder - .Factory(testFragment).create() + .newBuilder() .registerViewDataBinderWithSameModelType( inflateDataBinding = TestTextViewForStringWithDataBindingBinding::inflate, setViewModel = TestTextViewForStringWithDataBindingBinding::setViewModel @@ -544,11 +521,22 @@ class BindableAdapterTest { .build() } + private fun createSingleViewTypeWithDataBindingAndLiveDataAdapter(): + BindableAdapter { + return SingleTypeBuilder + .newBuilder() + .registerViewDataBinderWithSameModelType( + inflateDataBinding = TestTextViewForLiveDataWithDataBindingBinding::inflate, + setViewModel = TestTextViewForLiveDataWithDataBindingBinding::setViewModel + ) + .build() + } + private fun createSingleViewTypeWithDataBindingAndLiveDataAdapter( lifecycleOwner: Fragment ): BindableAdapter { return SingleTypeBuilder - .Factory(lifecycleOwner).create() + .newBuilder() .setLifecycleOwner(lifecycleOwner) .registerViewDataBinderWithSameModelType( inflateDataBinding = TestTextViewForLiveDataWithDataBindingBinding::inflate, @@ -557,10 +545,10 @@ class BindableAdapterTest { .build() } - private fun createMultiViewTypeNoDataBindingBindableAdapter(testFragment: Fragment): + private fun createMultiViewTypeNoDataBindingBindableAdapter(): BindableAdapter { return MultiTypeBuilder - .Factory(testFragment).create(ViewModelType.Companion::deriveTypeFrom) + .newBuilder(ViewModelType.Companion::deriveTypeFrom) .registerViewBinder( viewType = ViewModelType.STRING, inflateView = this::inflateTextViewForStringWithoutDataBinding, @@ -574,11 +562,33 @@ class BindableAdapterTest { .build() } + private fun createMultiViewTypeWithDataBindingBindableAdapter(): + BindableAdapter { + return MultiTypeBuilder + .newBuilder(ViewModelType.Companion::deriveTypeFrom) + .registerViewDataBinderWithSameModelType( + viewType = ViewModelType.STRING, + inflateDataBinding = TestTextViewForStringWithDataBindingBinding::inflate, + setViewModel = TestTextViewForStringWithDataBindingBinding::setViewModel + ) + .registerViewDataBinderWithSameModelType( + viewType = ViewModelType.INT, + inflateDataBinding = TestTextViewForIntWithDataBindingBinding::inflate, + setViewModel = TestTextViewForIntWithDataBindingBinding::setViewModel + ) + .registerViewDataBinderWithSameModelType( + viewType = ViewModelType.LIVE_DATA, + inflateDataBinding = TestTextViewForLiveDataWithDataBindingBinding::inflate, + setViewModel = TestTextViewForLiveDataWithDataBindingBinding::setViewModel + ) + .build() + } + private fun createMultiViewTypeWithDataBindingBindableAdapter( lifecycleOwner: Fragment ): BindableAdapter { return MultiTypeBuilder - .Factory(lifecycleOwner).create(ViewModelType.Companion::deriveTypeFrom) + .newBuilder(ViewModelType.Companion::deriveTypeFrom) .setLifecycleOwner(lifecycleOwner) .registerViewDataBinderWithSameModelType( viewType = ViewModelType.STRING, From ee9f2435fb29117d9e6a26f7b3841fe0bd55dfe6 Mon Sep 17 00:00:00 2001 From: kevingitonga Date: Tue, 5 Jul 2022 17:01:38 +0300 Subject: [PATCH 04/34] Inject the BindableAdapter.kt factory into the fragment and listview instances. --- .../ProfileAndDeviceIdFragment.kt | 1 + .../ProfileAndDeviceIdFragmentPresenter.kt | 6 +- .../CompletedStoryListFragmentPresenter.kt | 6 +- .../DeveloperOptionsFragmentPresenter.kt | 7 +- .../ForceNetworkTypeFragmentPresenter.kt | 6 +- .../MarkChaptersCompletedFragmentPresenter.kt | 6 +- .../MarkStoriesCompletedFragmentPresenter.kt | 6 +- .../MarkTopicsCompletedFragmentPresenter.kt | 6 +- .../ViewEventLogsFragmentPresenter.kt | 6 +- .../android/app/help/HelpFragmentPresenter.kt | 6 +- .../app/help/faq/FAQListFragmentPresenter.kt | 6 +- .../LicenseListFragmentPresenter.kt | 6 +- ...irdPartyDependencyListFragmentPresenter.kt | 6 +- ...HintsAndSolutionDialogFragmentPresenter.kt | 6 +- .../android/app/home/HomeFragmentPresenter.kt | 6 +- .../promotedlist/ComingSoonTopicsListView.kt | 5 +- .../promotedlist/PromotedStoryListView.kt | 5 +- .../onboarding/OnboardingFragmentPresenter.kt | 6 +- .../OngoingTopicListFragmentPresenter.kt | 6 +- .../options/AppLanguageFragmentPresenter.kt | 6 +- .../options/AudioLanguageFragmentPresenter.kt | 6 +- .../app/options/OptionsFragmentPresenter.kt | 6 +- .../ReadingTextSizeFragmentPresenter.kt | 6 +- .../state/DragDropSortInteractionView.kt | 9 +- .../player/state/SelectionInteractionView.kt | 6 +- .../ProfileChooserFragmentPresenter.kt | 6 +- .../ProfileProgressFragmentPresenter.kt | 6 +- .../app/recyclerview/BindableAdapter.kt | 61 +++++++++++-- .../profile/ProfileListFragmentPresenter.kt | 6 +- .../app/story/StoryFragmentPresenter.kt | 6 +- .../lessons/TopicLessonsFragmentPresenter.kt | 6 +- .../TopicPracticeFragmentPresenter.kt | 6 +- .../TopicRevisionFragmentPresenter.kt | 6 +- .../WalkthroughTopicListFragmentPresenter.kt | 6 +- .../app/recyclerview/BindableAdapterTest.kt | 87 ++++++++----------- 35 files changed, 194 insertions(+), 149 deletions(-) diff --git a/app/src/main/java/org/oppia/android/app/administratorcontrols/learneranalytics/ProfileAndDeviceIdFragment.kt b/app/src/main/java/org/oppia/android/app/administratorcontrols/learneranalytics/ProfileAndDeviceIdFragment.kt index 6bc0ad70c56..00901f64159 100644 --- a/app/src/main/java/org/oppia/android/app/administratorcontrols/learneranalytics/ProfileAndDeviceIdFragment.kt +++ b/app/src/main/java/org/oppia/android/app/administratorcontrols/learneranalytics/ProfileAndDeviceIdFragment.kt @@ -18,6 +18,7 @@ class ProfileAndDeviceIdFragment : InjectableFragment() { @Inject lateinit var profileAndDeviceIdFragmentPresenter: ProfileAndDeviceIdFragmentPresenter + override fun onAttach(context: Context) { super.onAttach(context) (fragmentComponent as FragmentComponentImpl).inject(this) diff --git a/app/src/main/java/org/oppia/android/app/administratorcontrols/learneranalytics/ProfileAndDeviceIdFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/administratorcontrols/learneranalytics/ProfileAndDeviceIdFragmentPresenter.kt index 41e95e106ba..d395dc57e00 100644 --- a/app/src/main/java/org/oppia/android/app/administratorcontrols/learneranalytics/ProfileAndDeviceIdFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/administratorcontrols/learneranalytics/ProfileAndDeviceIdFragmentPresenter.kt @@ -16,7 +16,8 @@ import javax.inject.Inject /** Presenter for arranging [ProfileAndDeviceIdFragment]'s UI. */ class ProfileAndDeviceIdFragmentPresenter @Inject constructor( private val fragment: Fragment, - private val profileListViewModelFactory: ProfileListViewModel.Factory + private val profileListViewModelFactory: ProfileListViewModel.Factory, + private val multiTypeBuilderFactory:BindableAdapter.MultiTypeBuilder.Factory ) { private lateinit var binding: ProfileAndDeviceIdFragmentBinding @@ -38,8 +39,7 @@ class ProfileAndDeviceIdFragmentPresenter @Inject constructor( } private fun createRecyclerViewAdapter(): BindableAdapter { - return BindableAdapter.MultiTypeBuilder - .Factory(fragment).create { viewModel -> + return multiTypeBuilderFactory.create { viewModel -> when (viewModel) { is DeviceIdItemViewModel -> ProfileListItemViewType.DEVICE_ID is ProfileLearnerIdItemViewModel -> ProfileListItemViewType.LEARNER_ID diff --git a/app/src/main/java/org/oppia/android/app/completedstorylist/CompletedStoryListFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/completedstorylist/CompletedStoryListFragmentPresenter.kt index bde9e3052af..7894e26f199 100644 --- a/app/src/main/java/org/oppia/android/app/completedstorylist/CompletedStoryListFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/completedstorylist/CompletedStoryListFragmentPresenter.kt @@ -17,7 +17,8 @@ import javax.inject.Inject class CompletedStoryListFragmentPresenter @Inject constructor( private val activity: AppCompatActivity, private val fragment: Fragment, - private val viewModelProvider: ViewModelProvider + private val viewModelProvider: ViewModelProvider, + private val singleTypeBuilderFactory:BindableAdapter.SingleTypeBuilder.Factory ) { private lateinit var binding: CompletedStoryListFragmentBinding @@ -52,8 +53,7 @@ class CompletedStoryListFragmentPresenter @Inject constructor( } private fun createRecyclerViewAdapter(): BindableAdapter { - return BindableAdapter.SingleTypeBuilder - .Factory(fragment).create() + return singleTypeBuilderFactory.create() .registerViewDataBinderWithSameModelType( inflateDataBinding = CompletedStoryItemBinding::inflate, setViewModel = CompletedStoryItemBinding::setViewModel diff --git a/app/src/main/java/org/oppia/android/app/devoptions/DeveloperOptionsFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/devoptions/DeveloperOptionsFragmentPresenter.kt index 2707ebf15fa..c0bcfbec803 100644 --- a/app/src/main/java/org/oppia/android/app/devoptions/DeveloperOptionsFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/devoptions/DeveloperOptionsFragmentPresenter.kt @@ -24,7 +24,9 @@ import javax.inject.Inject @FragmentScope class DeveloperOptionsFragmentPresenter @Inject constructor( private val activity: AppCompatActivity, - private val fragment: Fragment + private val fragment: Fragment, + private val multiTypeBuilderFactory:BindableAdapter.MultiTypeBuilder.Factory + ) { private lateinit var binding: DeveloperOptionsFragmentBinding @@ -59,8 +61,7 @@ class DeveloperOptionsFragmentPresenter @Inject constructor( } private fun createRecyclerViewAdapter(): BindableAdapter { - return BindableAdapter.MultiTypeBuilder - .Factory(fragment).create { viewModel -> + return multiTypeBuilderFactory.create { viewModel -> when (viewModel) { is DeveloperOptionsModifyLessonProgressViewModel -> { viewModel.itemIndex.set(0) diff --git a/app/src/main/java/org/oppia/android/app/devoptions/forcenetworktype/ForceNetworkTypeFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/devoptions/forcenetworktype/ForceNetworkTypeFragmentPresenter.kt index 5ac2b43629a..12c072e4fb1 100644 --- a/app/src/main/java/org/oppia/android/app/devoptions/forcenetworktype/ForceNetworkTypeFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/devoptions/forcenetworktype/ForceNetworkTypeFragmentPresenter.kt @@ -21,7 +21,8 @@ class ForceNetworkTypeFragmentPresenter @Inject constructor( private val activity: AppCompatActivity, private val fragment: Fragment, private val networkConnectionUtil: Optional, - private val viewModelProvider: ViewModelProvider + private val viewModelProvider: ViewModelProvider, + private val singleTypeBuilderFactory:BindableAdapter.SingleTypeBuilder.Factory ) { private lateinit var binding: ForceNetworkTypeFragmentBinding @@ -60,8 +61,7 @@ class ForceNetworkTypeFragmentPresenter @Inject constructor( } private fun createRecyclerViewAdapter(): BindableAdapter { - return BindableAdapter.SingleTypeBuilder - .Factory(fragment).create() + return singleTypeBuilderFactory.create() .registerViewDataBinderWithSameModelType( inflateDataBinding = ForceNetworkTypeNetworkItemViewBinding::inflate, setViewModel = this::bindNetworkItemView diff --git a/app/src/main/java/org/oppia/android/app/devoptions/markchapterscompleted/MarkChaptersCompletedFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/devoptions/markchapterscompleted/MarkChaptersCompletedFragmentPresenter.kt index e78d3f5f409..b8579e42b57 100644 --- a/app/src/main/java/org/oppia/android/app/devoptions/markchapterscompleted/MarkChaptersCompletedFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/devoptions/markchapterscompleted/MarkChaptersCompletedFragmentPresenter.kt @@ -23,7 +23,8 @@ class MarkChaptersCompletedFragmentPresenter @Inject constructor( private val activity: AppCompatActivity, private val fragment: Fragment, private val viewModelProvider: ViewModelProvider, - private val modifyLessonProgressController: ModifyLessonProgressController + private val modifyLessonProgressController: ModifyLessonProgressController, + private val multiTypeBuilderFactory:BindableAdapter.MultiTypeBuilder.Factory ) : ChapterSelector { private lateinit var binding: MarkChaptersCompletedFragmentBinding private lateinit var linearLayoutManager: LinearLayoutManager @@ -94,8 +95,7 @@ class MarkChaptersCompletedFragmentPresenter @Inject constructor( } private fun createRecyclerViewAdapter(): BindableAdapter { - return BindableAdapter.MultiTypeBuilder - .Factory(fragment).create { viewModel -> + return multiTypeBuilderFactory.create { viewModel -> when (viewModel) { is StorySummaryViewModel -> ViewType.VIEW_TYPE_STORY is ChapterSummaryViewModel -> ViewType.VIEW_TYPE_CHAPTER diff --git a/app/src/main/java/org/oppia/android/app/devoptions/markstoriescompleted/MarkStoriesCompletedFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/devoptions/markstoriescompleted/MarkStoriesCompletedFragmentPresenter.kt index b93cd810b7b..eb62c5ef750 100644 --- a/app/src/main/java/org/oppia/android/app/devoptions/markstoriescompleted/MarkStoriesCompletedFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/devoptions/markstoriescompleted/MarkStoriesCompletedFragmentPresenter.kt @@ -21,7 +21,8 @@ class MarkStoriesCompletedFragmentPresenter @Inject constructor( private val activity: AppCompatActivity, private val fragment: Fragment, private val viewModelProvider: ViewModelProvider, - private val modifyLessonProgressController: ModifyLessonProgressController + private val modifyLessonProgressController: ModifyLessonProgressController, + private val singleTypeBuilderFactory:BindableAdapter.SingleTypeBuilder.Factory ) : StorySelector { private lateinit var binding: MarkStoriesCompletedFragmentBinding private lateinit var linearLayoutManager: LinearLayoutManager @@ -91,8 +92,7 @@ class MarkStoriesCompletedFragmentPresenter @Inject constructor( } private fun createRecyclerViewAdapter(): BindableAdapter { - return BindableAdapter.SingleTypeBuilder - .Factory(fragment).create() + return singleTypeBuilderFactory.create() .registerViewDataBinderWithSameModelType( inflateDataBinding = MarkStoriesCompletedStorySummaryViewBinding::inflate, setViewModel = this::bindStorySummaryView diff --git a/app/src/main/java/org/oppia/android/app/devoptions/marktopicscompleted/MarkTopicsCompletedFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/devoptions/marktopicscompleted/MarkTopicsCompletedFragmentPresenter.kt index dce2187e36f..de73f50da00 100644 --- a/app/src/main/java/org/oppia/android/app/devoptions/marktopicscompleted/MarkTopicsCompletedFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/devoptions/marktopicscompleted/MarkTopicsCompletedFragmentPresenter.kt @@ -21,7 +21,8 @@ class MarkTopicsCompletedFragmentPresenter @Inject constructor( private val activity: AppCompatActivity, private val fragment: Fragment, private val viewModelProvider: ViewModelProvider, - private val modifyLessonProgressController: ModifyLessonProgressController + private val modifyLessonProgressController: ModifyLessonProgressController, + private val singleTypeAdapterFactory:BindableAdapter.SingleTypeBuilder.Factory ) : TopicSelector { private lateinit var binding: MarkTopicsCompletedFragmentBinding private lateinit var linearLayoutManager: LinearLayoutManager @@ -89,8 +90,7 @@ class MarkTopicsCompletedFragmentPresenter @Inject constructor( } private fun createRecyclerViewAdapter(): BindableAdapter { - return BindableAdapter.SingleTypeBuilder - .Factory(fragment).create() + return singleTypeAdapterFactory.create() .registerViewDataBinderWithSameModelType( inflateDataBinding = MarkTopicsCompletedTopicViewBinding::inflate, setViewModel = this::bindTopicSummaryView diff --git a/app/src/main/java/org/oppia/android/app/devoptions/vieweventlogs/ViewEventLogsFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/devoptions/vieweventlogs/ViewEventLogsFragmentPresenter.kt index 63fc8e5cc21..6c18bd4e4d3 100644 --- a/app/src/main/java/org/oppia/android/app/devoptions/vieweventlogs/ViewEventLogsFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/devoptions/vieweventlogs/ViewEventLogsFragmentPresenter.kt @@ -18,7 +18,8 @@ import javax.inject.Inject class ViewEventLogsFragmentPresenter @Inject constructor( private val activity: AppCompatActivity, private val fragment: Fragment, - private val viewModelProvider: ViewModelProvider + private val viewModelProvider: ViewModelProvider, + private val singleTypeAdapterFactory:BindableAdapter.SingleTypeBuilder.Factory ) { private lateinit var binding: ViewEventLogsFragmentBinding @@ -56,8 +57,7 @@ class ViewEventLogsFragmentPresenter @Inject constructor( } private fun createRecyclerViewAdapter(): BindableAdapter { - return BindableAdapter.SingleTypeBuilder - .Factory(fragment).create() + return singleTypeAdapterFactory.create() .registerViewDataBinderWithSameModelType( inflateDataBinding = ViewEventLogsEventLogItemViewBinding::inflate, setViewModel = ViewEventLogsEventLogItemViewBinding::setViewModel diff --git a/app/src/main/java/org/oppia/android/app/help/HelpFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/help/HelpFragmentPresenter.kt index 3ec43719f1a..74d0efc0ccf 100644 --- a/app/src/main/java/org/oppia/android/app/help/HelpFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/help/HelpFragmentPresenter.kt @@ -18,7 +18,8 @@ import javax.inject.Inject class HelpFragmentPresenter @Inject constructor( private val activity: AppCompatActivity, private val fragment: Fragment, - private val viewModelProvider: ViewModelProvider + private val viewModelProvider: ViewModelProvider, + private val singleTypeBuilderFactory:BindableAdapter.SingleTypeBuilder.Factory ) { private lateinit var binding: HelpFragmentBinding @@ -48,8 +49,7 @@ class HelpFragmentPresenter @Inject constructor( } private fun createRecyclerViewAdapter(): BindableAdapter { - return BindableAdapter.SingleTypeBuilder - .Factory(fragment).create() + return singleTypeBuilderFactory.create() .registerViewDataBinderWithSameModelType( inflateDataBinding = HelpItemBinding::inflate, setViewModel = HelpItemBinding::setViewModel diff --git a/app/src/main/java/org/oppia/android/app/help/faq/FAQListFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/help/faq/FAQListFragmentPresenter.kt index 90af115bbd2..1f7c525bc3f 100644 --- a/app/src/main/java/org/oppia/android/app/help/faq/FAQListFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/help/faq/FAQListFragmentPresenter.kt @@ -22,7 +22,8 @@ import javax.inject.Inject class FAQListFragmentPresenter @Inject constructor( private val activity: AppCompatActivity, private val fragment: Fragment, - private val viewModelProvider: ViewModelProvider + private val viewModelProvider: ViewModelProvider, + private val multiTypeBuilderFactory:BindableAdapter.MultiTypeBuilder.Factory ) { private lateinit var binding: FaqListFragmentBinding @@ -48,8 +49,7 @@ class FAQListFragmentPresenter @Inject constructor( } private fun createRecyclerViewAdapter(): BindableAdapter { - return BindableAdapter.MultiTypeBuilder - .Factory(fragment).create { viewModel -> + return multiTypeBuilderFactory.create { viewModel -> when (viewModel) { is FAQHeaderViewModel -> ViewType.VIEW_TYPE_HEADER is FAQContentViewModel -> ViewType.VIEW_TYPE_CONTENT diff --git a/app/src/main/java/org/oppia/android/app/help/thirdparty/LicenseListFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/help/thirdparty/LicenseListFragmentPresenter.kt index 8b5a25e3892..3bc5ed77cc3 100644 --- a/app/src/main/java/org/oppia/android/app/help/thirdparty/LicenseListFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/help/thirdparty/LicenseListFragmentPresenter.kt @@ -18,7 +18,8 @@ import javax.inject.Inject class LicenseListFragmentPresenter @Inject constructor( private val activity: AppCompatActivity, private val fragment: Fragment, - private val resourceHandler: AppLanguageResourceHandler + private val resourceHandler: AppLanguageResourceHandler, + private val singleTypeBuilderFactory:BindableAdapter.SingleTypeBuilder.Factory ) { private lateinit var binding: LicenseListFragmentBinding @@ -51,8 +52,7 @@ class LicenseListFragmentPresenter @Inject constructor( } private fun createRecyclerViewAdapter(): BindableAdapter { - return BindableAdapter.SingleTypeBuilder - .Factory(fragment).create() + return singleTypeBuilderFactory.create() .registerViewDataBinderWithSameModelType( inflateDataBinding = LicenseItemBinding::inflate, setViewModel = LicenseItemBinding::setViewModel diff --git a/app/src/main/java/org/oppia/android/app/help/thirdparty/ThirdPartyDependencyListFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/help/thirdparty/ThirdPartyDependencyListFragmentPresenter.kt index 4a982f3412c..60101d15cde 100644 --- a/app/src/main/java/org/oppia/android/app/help/thirdparty/ThirdPartyDependencyListFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/help/thirdparty/ThirdPartyDependencyListFragmentPresenter.kt @@ -18,7 +18,8 @@ import javax.inject.Inject class ThirdPartyDependencyListFragmentPresenter @Inject constructor( private val activity: AppCompatActivity, private val fragment: Fragment, - private val viewModelProvider: ViewModelProvider + private val viewModelProvider: ViewModelProvider, + private val singleTypeBuilderFactory:BindableAdapter.SingleTypeBuilder.Factory ) { private lateinit var binding: ThirdPartyDependencyListFragmentBinding @@ -50,8 +51,7 @@ class ThirdPartyDependencyListFragmentPresenter @Inject constructor( } private fun createRecyclerViewAdapter(): BindableAdapter { - return BindableAdapter.SingleTypeBuilder - .Factory(fragment).create() + return singleTypeBuilderFactory.create() .registerViewDataBinderWithSameModelType( inflateDataBinding = ThirdPartyDependencyItemBinding::inflate, setViewModel = ThirdPartyDependencyItemBinding::setViewModel diff --git a/app/src/main/java/org/oppia/android/app/hintsandsolution/HintsAndSolutionDialogFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/hintsandsolution/HintsAndSolutionDialogFragmentPresenter.kt index a7cbba9f8fc..4cc57dd7893 100644 --- a/app/src/main/java/org/oppia/android/app/hintsandsolution/HintsAndSolutionDialogFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/hintsandsolution/HintsAndSolutionDialogFragmentPresenter.kt @@ -36,7 +36,8 @@ class HintsAndSolutionDialogFragmentPresenter @Inject constructor( private val htmlParserFactory: HtmlParser.Factory, @DefaultResourceBucketName private val resourceBucketName: String, @ExplorationHtmlParserEntityType private val entityType: String, - private val resourceHandler: AppLanguageResourceHandler + private val resourceHandler: AppLanguageResourceHandler, + private val multiTypeAdapterFactory:BindableAdapter.MultiTypeBuilder.Factory ) { private var currentExpandedHintListIndex: Int? = null @@ -167,8 +168,7 @@ class HintsAndSolutionDialogFragmentPresenter @Inject constructor( } private fun createRecyclerViewAdapter(): BindableAdapter { - return BindableAdapter.MultiTypeBuilder - .Factory(fragment).create { viewModel -> + return multiTypeAdapterFactory.create { viewModel -> when (viewModel) { is HintsViewModel -> ViewType.VIEW_TYPE_HINT_ITEM is SolutionViewModel -> ViewType.VIEW_TYPE_SOLUTION_ITEM diff --git a/app/src/main/java/org/oppia/android/app/home/HomeFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/home/HomeFragmentPresenter.kt index 38e9f7de893..0d22e62ea01 100644 --- a/app/src/main/java/org/oppia/android/app/home/HomeFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/home/HomeFragmentPresenter.kt @@ -41,7 +41,8 @@ class HomeFragmentPresenter @Inject constructor( @TopicHtmlParserEntityType private val topicEntityType: String, @StoryHtmlParserEntityType private val storyEntityType: String, private val resourceHandler: AppLanguageResourceHandler, - private val dateTimeUtil: DateTimeUtil + private val dateTimeUtil: DateTimeUtil, + private val multiTypeAdapterFactory:BindableAdapter.MultiTypeBuilder.Factory ) { private val routeToTopicListener = activity as RouteToTopicListener private lateinit var binding: HomeFragmentBinding @@ -93,8 +94,7 @@ class HomeFragmentPresenter @Inject constructor( } private fun createRecyclerViewAdapter(): BindableAdapter { - return BindableAdapter.MultiTypeBuilder - .Factory(fragment).create { viewModel -> + return multiTypeAdapterFactory.create { viewModel -> when (viewModel) { is WelcomeViewModel -> ViewType.WELCOME_MESSAGE is PromotedStoryListViewModel -> ViewType.PROMOTED_STORY_LIST diff --git a/app/src/main/java/org/oppia/android/app/home/promotedlist/ComingSoonTopicsListView.kt b/app/src/main/java/org/oppia/android/app/home/promotedlist/ComingSoonTopicsListView.kt index 8d35a0bc30b..4e9cee12433 100644 --- a/app/src/main/java/org/oppia/android/app/home/promotedlist/ComingSoonTopicsListView.kt +++ b/app/src/main/java/org/oppia/android/app/home/promotedlist/ComingSoonTopicsListView.kt @@ -35,6 +35,9 @@ class ComingSoonTopicsListView @JvmOverloads constructor( @Inject lateinit var fragment: Fragment + @Inject + lateinit var singleTypeAdapterFactory:BindableAdapter.SingleTypeBuilder.Factory + override fun onAttachedToWindow() { super.onAttachedToWindow() @@ -72,7 +75,7 @@ class ComingSoonTopicsListView @JvmOverloads constructor( } private fun createAdapter(): BindableAdapter { - return BindableAdapter.SingleTypeBuilder.Factory(fragment).create() + return singleTypeAdapterFactory.create() .registerViewBinder( inflateView = { parent -> bindingInterface.provideComingSoonTopicViewInflatedView( diff --git a/app/src/main/java/org/oppia/android/app/home/promotedlist/PromotedStoryListView.kt b/app/src/main/java/org/oppia/android/app/home/promotedlist/PromotedStoryListView.kt index 5869d5bc762..b877ca867a6 100644 --- a/app/src/main/java/org/oppia/android/app/home/promotedlist/PromotedStoryListView.kt +++ b/app/src/main/java/org/oppia/android/app/home/promotedlist/PromotedStoryListView.kt @@ -35,6 +35,9 @@ class PromotedStoryListView @JvmOverloads constructor( @Inject lateinit var fragment: Fragment + @Inject + lateinit var singleTypeBuilderFactory:BindableAdapter.SingleTypeBuilder.Factory + override fun onAttachedToWindow() { super.onAttachedToWindow() @@ -72,7 +75,7 @@ class PromotedStoryListView @JvmOverloads constructor( } private fun createAdapter(): BindableAdapter { - return BindableAdapter.SingleTypeBuilder.Factory(fragment).create() + return singleTypeBuilderFactory.create() .registerViewBinder( inflateView = { parent -> bindingInterface.providePromotedStoryCardInflatedView( diff --git a/app/src/main/java/org/oppia/android/app/onboarding/OnboardingFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/onboarding/OnboardingFragmentPresenter.kt index f6d46d97ba1..55094934e92 100644 --- a/app/src/main/java/org/oppia/android/app/onboarding/OnboardingFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/onboarding/OnboardingFragmentPresenter.kt @@ -26,7 +26,8 @@ class OnboardingFragmentPresenter @Inject constructor( private val fragment: Fragment, private val viewModelProvider: ViewModelProvider, private val viewModelProviderFinalSlide: ViewModelProvider, - private val resourceHandler: AppLanguageResourceHandler + private val resourceHandler: AppLanguageResourceHandler, + private val multiTypeBuilderFactory:BindableAdapter.MultiTypeBuilder.Factory ) : OnboardingNavigationListener { private val dotsList = ArrayList() private lateinit var binding: OnboardingFragmentBinding @@ -95,8 +96,7 @@ class OnboardingFragmentPresenter @Inject constructor( } private fun createViewPagerAdapter(): BindableAdapter { - return BindableAdapter.MultiTypeBuilder - .Factory(fragment).create { viewModel -> + return multiTypeBuilderFactory.create { viewModel -> when (viewModel) { is OnboardingSlideViewModel -> ViewType.ONBOARDING_MIDDLE_SLIDE is OnboardingSlideFinalViewModel -> ViewType.ONBOARDING_FINAL_SLIDE diff --git a/app/src/main/java/org/oppia/android/app/ongoingtopiclist/OngoingTopicListFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/ongoingtopiclist/OngoingTopicListFragmentPresenter.kt index 7727317cb9e..d2534868fdc 100644 --- a/app/src/main/java/org/oppia/android/app/ongoingtopiclist/OngoingTopicListFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/ongoingtopiclist/OngoingTopicListFragmentPresenter.kt @@ -17,7 +17,8 @@ import javax.inject.Inject class OngoingTopicListFragmentPresenter @Inject constructor( private val activity: AppCompatActivity, private val fragment: Fragment, - private val viewModelProvider: ViewModelProvider + private val viewModelProvider: ViewModelProvider, + private val singleTypeBuilderFactory:BindableAdapter.SingleTypeBuilder.Factory ) { private lateinit var binding: OngoingTopicListFragmentBinding @@ -56,8 +57,7 @@ class OngoingTopicListFragmentPresenter @Inject constructor( } private fun createRecyclerViewAdapter(): BindableAdapter { - return BindableAdapter.SingleTypeBuilder - .Factory(fragment).create() + return singleTypeBuilderFactory.create() .registerViewDataBinderWithSameModelType( inflateDataBinding = OngoingTopicItemBinding::inflate, setViewModel = OngoingTopicItemBinding::setViewModel diff --git a/app/src/main/java/org/oppia/android/app/options/AppLanguageFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/options/AppLanguageFragmentPresenter.kt index b6d0985521b..1433ae55098 100644 --- a/app/src/main/java/org/oppia/android/app/options/AppLanguageFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/options/AppLanguageFragmentPresenter.kt @@ -12,7 +12,8 @@ import javax.inject.Inject /** The presenter for [AppLanguageFragment]. */ class AppLanguageFragmentPresenter @Inject constructor( private val fragment: Fragment, - private val languageSelectionViewModel: LanguageSelectionViewModel + private val languageSelectionViewModel: LanguageSelectionViewModel, + private val singleTypeBuilderFactory: BindableAdapter.SingleTypeBuilder.Factory ) { private lateinit var prefSummaryValue: String fun handleOnCreateView( @@ -41,8 +42,7 @@ class AppLanguageFragmentPresenter @Inject constructor( } private fun createRecyclerViewAdapter(): BindableAdapter { - return BindableAdapter.SingleTypeBuilder - .Factory(fragment).create() + return singleTypeBuilderFactory.create() .registerViewDataBinderWithSameModelType( inflateDataBinding = LanguageItemsBinding::inflate, setViewModel = LanguageItemsBinding::setViewModel diff --git a/app/src/main/java/org/oppia/android/app/options/AudioLanguageFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/options/AudioLanguageFragmentPresenter.kt index ccc96db1f26..f891ee395d1 100644 --- a/app/src/main/java/org/oppia/android/app/options/AudioLanguageFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/options/AudioLanguageFragmentPresenter.kt @@ -12,7 +12,8 @@ import javax.inject.Inject /** The presenter for [AudioLanguageFragment]. */ class AudioLanguageFragmentPresenter @Inject constructor( private val fragment: Fragment, - private val languageSelectionViewModel: LanguageSelectionViewModel + private val languageSelectionViewModel: LanguageSelectionViewModel, + private val singleTypeBuilderFactory:BindableAdapter.SingleTypeBuilder.Factory ) { private lateinit var prefSummaryValue: String fun handleOnCreateView( @@ -41,8 +42,7 @@ class AudioLanguageFragmentPresenter @Inject constructor( } private fun createRecyclerViewAdapter(): BindableAdapter { - return BindableAdapter.SingleTypeBuilder - .Factory(fragment).create() + return singleTypeBuilderFactory.create() .registerViewDataBinderWithSameModelType( inflateDataBinding = LanguageItemsBinding::inflate, setViewModel = LanguageItemsBinding::setViewModel diff --git a/app/src/main/java/org/oppia/android/app/options/OptionsFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/options/OptionsFragmentPresenter.kt index e30832ffb98..b0a64c9fd5e 100644 --- a/app/src/main/java/org/oppia/android/app/options/OptionsFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/options/OptionsFragmentPresenter.kt @@ -46,7 +46,8 @@ class OptionsFragmentPresenter @Inject constructor( private val fragment: Fragment, private val profileManagementController: ProfileManagementController, private val viewModelProvider: ViewModelProvider, - private val oppiaLogger: OppiaLogger + private val oppiaLogger: OppiaLogger, + private val multiTypeBuilderFactory:BindableAdapter.MultiTypeBuilder.Factory ) { private lateinit var binding: OptionsFragmentBinding private lateinit var recyclerViewAdapter: RecyclerView.Adapter<*> @@ -94,8 +95,7 @@ class OptionsFragmentPresenter @Inject constructor( private fun createRecyclerViewAdapter( isMultipane: Boolean ): BindableAdapter { - return BindableAdapter.MultiTypeBuilder - .Factory(fragment).create { viewModel -> + return multiTypeBuilderFactory.create { viewModel -> viewModel.isMultipane.set(isMultipane) when (viewModel) { is OptionsReadingTextSizeViewModel -> { diff --git a/app/src/main/java/org/oppia/android/app/options/ReadingTextSizeFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/options/ReadingTextSizeFragmentPresenter.kt index 9a17ab4cc9c..4bf0dd56eb9 100644 --- a/app/src/main/java/org/oppia/android/app/options/ReadingTextSizeFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/options/ReadingTextSizeFragmentPresenter.kt @@ -15,7 +15,8 @@ import javax.inject.Inject class ReadingTextSizeFragmentPresenter @Inject constructor( private val fragment: Fragment, private val readingTextSizeSelectionViewModel: ReadingTextSizeSelectionViewModel, - resourceHandler: AppLanguageResourceHandler + resourceHandler: AppLanguageResourceHandler, + private val singleTypeBuilderFactory:BindableAdapter.SingleTypeBuilder.Factory ) { private var fontSize: String = resourceHandler.getStringInLocale( R.string.reading_text_size_medium @@ -48,8 +49,7 @@ class ReadingTextSizeFragmentPresenter @Inject constructor( } private fun createRecyclerViewAdapter(): BindableAdapter { - return BindableAdapter.SingleTypeBuilder - .Factory(fragment).create() + return singleTypeBuilderFactory.create() .registerViewDataBinderWithSameModelType( inflateDataBinding = TextSizeItemsBinding::inflate, setViewModel = TextSizeItemsBinding::setViewModel diff --git a/app/src/main/java/org/oppia/android/app/player/state/DragDropSortInteractionView.kt b/app/src/main/java/org/oppia/android/app/player/state/DragDropSortInteractionView.kt index eaf60347c60..745161ba8c8 100644 --- a/app/src/main/java/org/oppia/android/app/player/state/DragDropSortInteractionView.kt +++ b/app/src/main/java/org/oppia/android/app/player/state/DragDropSortInteractionView.kt @@ -56,6 +56,9 @@ class DragDropSortInteractionView @JvmOverloads constructor( @Inject lateinit var fragment: Fragment + @Inject + lateinit var singleTypeBuilderFactory:BindableAdapter.SingleTypeBuilder.Factory + private lateinit var entityId: String private lateinit var onDragEnd: OnDragEndedListener private lateinit var onItemDrag: OnItemDragListener @@ -86,8 +89,7 @@ class DragDropSortInteractionView @JvmOverloads constructor( } private fun createAdapter(): BindableAdapter { - return BindableAdapter.SingleTypeBuilder - .Factory(fragment).create() + return singleTypeBuilderFactory.create() .registerViewBinder( inflateView = { parent -> viewBindingShim.provideDragDropSortInteractionInflatedView( @@ -114,8 +116,7 @@ class DragDropSortInteractionView @JvmOverloads constructor( } private fun createNestedAdapter(): BindableAdapter { - return BindableAdapter.SingleTypeBuilder - .Factory(fragment).create() + return singleTypeBuilderFactory.create() .registerViewBinder( inflateView = { parent -> viewBindingShim.provideDragDropSingleItemInflatedView( diff --git a/app/src/main/java/org/oppia/android/app/player/state/SelectionInteractionView.kt b/app/src/main/java/org/oppia/android/app/player/state/SelectionInteractionView.kt index 3b14bf91711..b4f6732b55c 100644 --- a/app/src/main/java/org/oppia/android/app/player/state/SelectionInteractionView.kt +++ b/app/src/main/java/org/oppia/android/app/player/state/SelectionInteractionView.kt @@ -48,6 +48,9 @@ class SelectionInteractionView @JvmOverloads constructor( @Inject lateinit var fragment: Fragment + @Inject + lateinit var singleTypeBuilderFactory:BindableAdapter.SingleTypeBuilder.Factory + private lateinit var entityId: String private lateinit var writtenTranslationContext: WrittenTranslationContext @@ -86,8 +89,7 @@ class SelectionInteractionView @JvmOverloads constructor( private fun createAdapter(): BindableAdapter { return when (selectionItemInputType) { SelectionItemInputType.CHECKBOXES -> - BindableAdapter.SingleTypeBuilder - .Factory(fragment).create() + singleTypeBuilderFactory.create() .registerViewBinder( inflateView = { parent -> bindingInterface.provideSelectionInteractionViewInflatedView( diff --git a/app/src/main/java/org/oppia/android/app/profile/ProfileChooserFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/profile/ProfileChooserFragmentPresenter.kt index 6f6a55e1b5d..28b41c2adbf 100644 --- a/app/src/main/java/org/oppia/android/app/profile/ProfileChooserFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/profile/ProfileChooserFragmentPresenter.kt @@ -65,7 +65,8 @@ class ProfileChooserFragmentPresenter @Inject constructor( private val context: Context, private val viewModelProvider: ViewModelProvider, private val profileManagementController: ProfileManagementController, - private val oppiaLogger: OppiaLogger + private val oppiaLogger: OppiaLogger, + private val multiTypeAdapterBuilder:BindableAdapter.MultiTypeBuilder.Factory ) { private lateinit var binding: ProfileChooserFragmentBinding val hasProfileEverBeenAddedValue = ObservableField(true) @@ -148,8 +149,7 @@ class ProfileChooserFragmentPresenter @Inject constructor( } private fun createRecyclerViewAdapter(): BindableAdapter { - return BindableAdapter.MultiTypeBuilder - .Factory(fragment).create( + return multiTypeAdapterBuilder.create( ProfileChooserUiModel::getModelTypeCase ) .registerViewDataBinderWithSameModelType( diff --git a/app/src/main/java/org/oppia/android/app/profileprogress/ProfileProgressFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/profileprogress/ProfileProgressFragmentPresenter.kt index f696d802ff1..af6ecab10da 100644 --- a/app/src/main/java/org/oppia/android/app/profileprogress/ProfileProgressFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/profileprogress/ProfileProgressFragmentPresenter.kt @@ -26,6 +26,9 @@ class ProfileProgressFragmentPresenter @Inject constructor( @Inject lateinit var viewModel: ProfileProgressViewModel + @Inject + lateinit var multiTypeAdapterBuilder:BindableAdapter.MultiTypeBuilder.Factory + fun handleCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -70,8 +73,7 @@ class ProfileProgressFragmentPresenter @Inject constructor( } private fun createRecyclerViewAdapter(): BindableAdapter { - return BindableAdapter.MultiTypeBuilder - .Factory(fragment).create { viewModel -> + return multiTypeAdapterBuilder.create { viewModel -> when (viewModel) { is ProfileProgressHeaderViewModel -> ViewType.VIEW_TYPE_HEADER is RecentlyPlayedStorySummaryViewModel -> ViewType.VIEW_TYPE_RECENTLY_PLAYED_STORY diff --git a/app/src/main/java/org/oppia/android/app/recyclerview/BindableAdapter.kt b/app/src/main/java/org/oppia/android/app/recyclerview/BindableAdapter.kt index 8cfdf2ea56f..242a12b8558 100644 --- a/app/src/main/java/org/oppia/android/app/recyclerview/BindableAdapter.kt +++ b/app/src/main/java/org/oppia/android/app/recyclerview/BindableAdapter.kt @@ -1,11 +1,14 @@ package org.oppia.android.app.recyclerview +import android.app.Activity import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.databinding.ViewDataBinding import androidx.fragment.app.Fragment +import androidx.lifecycle.LifecycleOwner import androidx.recyclerview.widget.RecyclerView +import java.lang.ref.WeakReference import javax.inject.Inject import kotlin.reflect.KClass @@ -93,6 +96,55 @@ class BindableAdapter internal constructor( internal abstract fun bind(data: T) } + /** + * The base builder for [BindableAdapter]. This class should not be used directly--use either + * [SingleTypeBuilder] or [MultiTypeBuilder] instead. + */ + abstract class BaseBuilder { + /** + * A [WeakReference] to a [LifecycleOwner] for databinding inflation. See [setLifecycleOwner]. + * Note that this needs to be a weak reference so that long-held references to the adapter do + * not potentially leak lifecycle owners (such as fragments and activities). + */ + private var lifecycleOwnerRef: WeakReference? = null + + /** + * Sets the [LifecycleOwner] corresponding to this adapter. This will automatically be used as + * the lifecycle owner for all databinding classes created during view inflation. Note that the + * adapter holds a weak reference to the owner to ensure long-lived references to the adapter + * class itself does not result in leaks, however it's up to the caller's responsibility to make + * sure that the adapter itself is not actually used after the lifecycle owner has expired + * (otherwise the app may crash). + * + * @return this + */ + fun setLifecycleOwner(lifecycleOwner: LifecycleOwner): BuilderType { + check(lifecycleOwnerRef == null) { + "A lifecycle owner has already been bound to this adapter." + } + lifecycleOwnerRef = WeakReference(lifecycleOwner) + + // This cast is not, strictly speaking, safe, but child classes are expected to inherit from + // the builder & pass their own type in. + @Suppress("UNCHECKED_CAST") return this as BuilderType + } + + /** + * Returns the [LifecycleOwner] bound to this adapter, or null if there isn't one. This method + * will throw if there was a lifecycle owner bound but is now expired. + */ + protected fun getLifecycleOwner(): LifecycleOwner? { + // Crash if the lifecycle owner has been cleaned up since it's not valid to use the adapter + // with an old lifecycle owner, and silently ignoring this may result in part of the layout + // not responding to events. + return lifecycleOwnerRef?.let { ref -> + checkNotNull(ref.get()) { + "Attempted to inflate data binding with expired lifecycle owner" + } + } + } + } + /** * Constructs a new [BindableAdapter] that for a single view type. * @@ -101,7 +153,7 @@ class BindableAdapter internal constructor( class SingleTypeBuilder( private val dataClassType: KClass, private val fragment: Fragment - ) { + ) : BaseBuilder>() { private lateinit var viewHolderFactory: ViewHolderFactory /** @@ -172,7 +224,7 @@ class BindableAdapter internal constructor( viewGroup, /* attachToRoot= */ false ) - binding.lifecycleOwner = fragment + binding.lifecycleOwner = getLifecycleOwner() object : BindableViewHolder(binding.root) { override fun bind(data: T) { setViewModel(binding, data) @@ -211,7 +263,7 @@ class BindableAdapter internal constructor( private val dataClassType: KClass, private val computeViewType: ComputeViewType, private val fragment: Fragment - ) { + ) : BaseBuilder>() { private var viewHolderFactoryMap: MutableMap> = HashMap() /** @@ -295,8 +347,7 @@ class BindableAdapter internal constructor( viewGroup, /* attachToRoot= */ false ) - - binding.lifecycleOwner = fragment + binding.lifecycleOwner = getLifecycleOwner() object : BindableViewHolder(binding.root) { override fun bind(data: T) { setViewModel(binding, transformViewModel(data)) diff --git a/app/src/main/java/org/oppia/android/app/settings/profile/ProfileListFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/settings/profile/ProfileListFragmentPresenter.kt index 5cd7f718e56..e6ceeb4950d 100644 --- a/app/src/main/java/org/oppia/android/app/settings/profile/ProfileListFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/settings/profile/ProfileListFragmentPresenter.kt @@ -19,7 +19,8 @@ import javax.inject.Inject class ProfileListFragmentPresenter @Inject constructor( private val activity: AppCompatActivity, private val fragment: Fragment, - private val viewModelProvider: ViewModelProvider + private val viewModelProvider: ViewModelProvider, + private val singleTypeAdapterBuilder:BindableAdapter.SingleTypeBuilder.Factory ) { private var isMultipane = false @@ -52,8 +53,7 @@ class ProfileListFragmentPresenter @Inject constructor( } private fun createRecyclerViewAdapter(): BindableAdapter { - return BindableAdapter.SingleTypeBuilder - .Factory(fragment).create() + return singleTypeAdapterBuilder.create() .registerViewDataBinderWithSameModelType( inflateDataBinding = ProfileListProfileViewBinding::inflate, setViewModel = ::bindProfileView diff --git a/app/src/main/java/org/oppia/android/app/story/StoryFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/story/StoryFragmentPresenter.kt index ce2f82c50af..9976baa3b01 100644 --- a/app/src/main/java/org/oppia/android/app/story/StoryFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/story/StoryFragmentPresenter.kt @@ -49,7 +49,8 @@ class StoryFragmentPresenter @Inject constructor( private val explorationDataController: ExplorationDataController, @DefaultResourceBucketName private val resourceBucketName: String, @TopicHtmlParserEntityType private val entityType: String, - private val resourceHandler: AppLanguageResourceHandler + private val resourceHandler: AppLanguageResourceHandler, + private val multiTypeAdapterFactory:BindableAdapter.MultiTypeBuilder.Factory ) { private val routeToExplorationListener = activity as RouteToExplorationListener private val routeToResumeLessonListener = activity as RouteToResumeLessonListener @@ -135,8 +136,7 @@ class StoryFragmentPresenter @Inject constructor( } private fun createRecyclerViewAdapter(): BindableAdapter { - return BindableAdapter.MultiTypeBuilder - .Factory(fragment).create { viewModel -> + return multiTypeAdapterFactory.create { viewModel -> when (viewModel) { is StoryHeaderViewModel -> ViewType.VIEW_TYPE_HEADER is StoryChapterSummaryViewModel -> ViewType.VIEW_TYPE_CHAPTER diff --git a/app/src/main/java/org/oppia/android/app/topic/lessons/TopicLessonsFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/topic/lessons/TopicLessonsFragmentPresenter.kt index 82bc439ca76..ff5bdd7f83c 100644 --- a/app/src/main/java/org/oppia/android/app/topic/lessons/TopicLessonsFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/topic/lessons/TopicLessonsFragmentPresenter.kt @@ -34,7 +34,8 @@ class TopicLessonsFragmentPresenter @Inject constructor( private val fragment: Fragment, private val oppiaLogger: OppiaLogger, private val explorationDataController: ExplorationDataController, - private val explorationCheckpointController: ExplorationCheckpointController + private val explorationCheckpointController: ExplorationCheckpointController, + private val multiTypeAdapterFactory:BindableAdapter.MultiTypeBuilder.Factory ) { private val routeToResumeLessonListener = activity as RouteToResumeLessonListener @@ -104,8 +105,7 @@ class TopicLessonsFragmentPresenter @Inject constructor( } private fun createRecyclerViewAdapter(): BindableAdapter { - return BindableAdapter.MultiTypeBuilder - .Factory(fragment).create { viewModel -> + return multiTypeAdapterFactory.create { viewModel -> when (viewModel) { is StorySummaryViewModel -> ViewType.VIEW_TYPE_STORY_ITEM is TopicLessonsTitleViewModel -> ViewType.VIEW_TYPE_TITLE_TEXT diff --git a/app/src/main/java/org/oppia/android/app/topic/practice/TopicPracticeFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/topic/practice/TopicPracticeFragmentPresenter.kt index a0b680b05be..9622076cf45 100644 --- a/app/src/main/java/org/oppia/android/app/topic/practice/TopicPracticeFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/topic/practice/TopicPracticeFragmentPresenter.kt @@ -27,7 +27,8 @@ class TopicPracticeFragmentPresenter @Inject constructor( private val activity: AppCompatActivity, private val fragment: Fragment, private val oppiaLogger: OppiaLogger, - private val viewModelProvider: ViewModelProvider + private val viewModelProvider: ViewModelProvider, + private val multiTypeAdapterFactory:BindableAdapter.MultiTypeBuilder.Factory ) : SubtopicSelector { private lateinit var binding: TopicPracticeFragmentBinding private lateinit var linearLayoutManager: LinearLayoutManager @@ -73,8 +74,7 @@ class TopicPracticeFragmentPresenter @Inject constructor( } private fun createRecyclerViewAdapter(): BindableAdapter { - return BindableAdapter.MultiTypeBuilder - .Factory(fragment).create { viewModel -> + return multiTypeAdapterFactory.create { viewModel -> when (viewModel) { is TopicPracticeHeaderViewModel -> ViewType.VIEW_TYPE_HEADER is TopicPracticeSubtopicViewModel -> ViewType.VIEW_TYPE_SKILL diff --git a/app/src/main/java/org/oppia/android/app/topic/revision/TopicRevisionFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/topic/revision/TopicRevisionFragmentPresenter.kt index 3ed104f392d..a9a0c8769c9 100755 --- a/app/src/main/java/org/oppia/android/app/topic/revision/TopicRevisionFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/topic/revision/TopicRevisionFragmentPresenter.kt @@ -22,7 +22,8 @@ import javax.inject.Inject class TopicRevisionFragmentPresenter @Inject constructor( activity: AppCompatActivity, private val fragment: Fragment, - private val viewModelProvider: ViewModelProvider + private val viewModelProvider: ViewModelProvider, + private val singleTypeAdapterFactory:BindableAdapter.SingleTypeBuilder.Factory ) : RevisionSubtopicSelector { private lateinit var binding: TopicRevisionFragmentBinding private var internalProfileId: Int = -1 @@ -70,8 +71,7 @@ class TopicRevisionFragmentPresenter @Inject constructor( } private fun createRecyclerViewAdapter(): BindableAdapter { - return BindableAdapter.SingleTypeBuilder - .Factory(fragment).create() + return singleTypeAdapterFactory.create() .registerViewDataBinderWithSameModelType( inflateDataBinding = TopicRevisionSummaryViewBinding::inflate, setViewModel = TopicRevisionSummaryViewBinding::setViewModel diff --git a/app/src/main/java/org/oppia/android/app/walkthrough/topiclist/WalkthroughTopicListFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/walkthrough/topiclist/WalkthroughTopicListFragmentPresenter.kt index d46bc65e7b8..254d930da21 100644 --- a/app/src/main/java/org/oppia/android/app/walkthrough/topiclist/WalkthroughTopicListFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/walkthrough/topiclist/WalkthroughTopicListFragmentPresenter.kt @@ -26,7 +26,8 @@ import javax.inject.Inject class WalkthroughTopicListFragmentPresenter @Inject constructor( val activity: AppCompatActivity, private val fragment: Fragment, - private val viewModelProvider: ViewModelProvider + private val viewModelProvider: ViewModelProvider, + private val multiTypeAdapterFactory: BindableAdapter.MultiTypeBuilder.Factory ) { private lateinit var binding: WalkthroughTopicListFragmentBinding private val routeToNextPage = activity as WalkthroughFragmentChangeListener @@ -73,8 +74,7 @@ class WalkthroughTopicListFragmentPresenter @Inject constructor( } private fun createRecyclerViewAdapter(): BindableAdapter { - return BindableAdapter.MultiTypeBuilder - .Factory(fragment).create { viewModel -> + return multiTypeAdapterFactory.create { viewModel -> when (viewModel) { is WalkthroughTopicHeaderViewModel -> ViewType.VIEW_TYPE_HEADER is WalkthroughTopicSummaryViewModel -> ViewType.VIEW_TYPE_TOPIC diff --git a/app/src/sharedTest/java/org/oppia/android/app/recyclerview/BindableAdapterTest.kt b/app/src/sharedTest/java/org/oppia/android/app/recyclerview/BindableAdapterTest.kt index a207a2e9a59..1fdcb401f72 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/recyclerview/BindableAdapterTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/recyclerview/BindableAdapterTest.kt @@ -144,6 +144,7 @@ class BindableAdapterTest { @Inject lateinit var testCoroutineDispatchers: TestCoroutineDispatchers + @Before fun setUp() { setUpTestApplicationComponent() @@ -163,8 +164,9 @@ class BindableAdapterTest { @Test fun testSingleTypeAdapter_withOneViewType_noData_bindsNoViews() { + val testFragment = Fragment() // Set up the adapter to be used for this test. - TestModule.testAdapterFactory = { createSingleViewTypeNoDataBindingBindableAdapter() } + TestModule.testAdapterFactory = { createSingleViewTypeNoDataBindingBindableAdapter(testFragment) } launch(BindableAdapterTestActivity::class.java).use { scenario -> scenario.onActivity { activity -> @@ -178,8 +180,9 @@ class BindableAdapterTest { @Test fun testSingleTypeAdapter_withOneViewType_setItem_automaticallyBindsView() { + val testFragment = Fragment() // Set up the adapter to be used for this test. - TestModule.testAdapterFactory = { createSingleViewTypeNoDataBindingBindableAdapter() } + TestModule.testAdapterFactory = { createSingleViewTypeNoDataBindingBindableAdapter(testFragment) } launch(BindableAdapterTestActivity::class.java).use { scenario -> scenario.onActivity { activity -> @@ -201,8 +204,9 @@ class BindableAdapterTest { @Test fun testSingleTypeAdapter_withOneViewType_nullData_bindsNoViews() { + val testFragment = Fragment() // Set up the adapter to be used for this test. - TestModule.testAdapterFactory = { createSingleViewTypeNoDataBindingBindableAdapter() } + TestModule.testAdapterFactory = { createSingleViewTypeNoDataBindingBindableAdapter(testFragment) } launch(BindableAdapterTestActivity::class.java).use { scenario -> scenario.onActivity { activity -> @@ -215,8 +219,9 @@ class BindableAdapterTest { @Test fun testSingleTypeAdapter_withOneViewType_setMultipleItems_automaticallyBinds() { + val testFragment = Fragment() // Set up the adapter to be used for this test. - TestModule.testAdapterFactory = { createSingleViewTypeNoDataBindingBindableAdapter() } + TestModule.testAdapterFactory = { createSingleViewTypeNoDataBindingBindableAdapter(testFragment) } launch(BindableAdapterTestActivity::class.java).use { scenario -> scenario.onActivity { activity -> @@ -241,8 +246,9 @@ class BindableAdapterTest { @Test fun testMultiTypeAdapter_withTwoViewTypes_setItems_autoBindsCorrectItemsPerTypes() { + val testFragment = Fragment() // Set up the adapter to be used for this test. - TestModule.testAdapterFactory = { createMultiViewTypeNoDataBindingBindableAdapter() } + TestModule.testAdapterFactory = { createMultiViewTypeNoDataBindingBindableAdapter(testFragment) } launch(BindableAdapterTestActivity::class.java).use { scenario -> scenario.onActivity { activity -> @@ -277,8 +283,9 @@ class BindableAdapterTest { @Test fun testSingleTypeAdapter_withOneDataBoundViewType_setItem_automaticallyBindsView() { + val testFragment = Fragment() // Set up the adapter to be used for this test. - TestModule.testAdapterFactory = { createSingleViewTypeWithDataBindingBindableAdapter() } + TestModule.testAdapterFactory = { createSingleViewTypeWithDataBindingBindableAdapter(testFragment) } launch(BindableAdapterTestActivity::class.java).use { scenario -> scenario.onActivity { activity -> @@ -300,8 +307,12 @@ class BindableAdapterTest { @Test fun testSingleTypeAdapter_withTwoDataBoundViewTypes_setItems_autoBindsCorrectItemsPerTypes() { + val testFragment = Fragment() // Set up the adapter to be used for this test. - TestModule.testAdapterFactory = { createSingleViewTypeWithDataBindingBindableAdapter() } + TestModule.testAdapterFactory = { createSingleViewTypeWithDataBindingBindableAdapter( + testFragment + ) + } launch(BindableAdapterTestActivity::class.java).use { scenario -> scenario.onActivity { activity -> @@ -327,8 +338,9 @@ class BindableAdapterTest { @Test fun testMultiTypeAdapter_partiallyDataBoundViewTypes_setItems_autoBindsCorrectItemsPerTypes() { + val testFragment = Fragment() // Set up the adapter to be used for this test. - TestModule.testAdapterFactory = { createMultiViewTypeWithDataBindingBindableAdapter() } + TestModule.testAdapterFactory = { createMultiViewTypeWithDataBindingBindableAdapter(testFragment) } launch(BindableAdapterTestActivity::class.java).use { scenario -> scenario.onActivity { activity -> @@ -367,7 +379,7 @@ class BindableAdapterTest { val exception = assertThrows(IllegalStateException::class) { SingleTypeBuilder - .newBuilder() + .Factory(testFragment).create() .setLifecycleOwner(testFragment) .setLifecycleOwner(testFragment) .build() @@ -381,7 +393,7 @@ class BindableAdapterTest { val exception = assertThrows(IllegalStateException::class) { MultiTypeBuilder - .newBuilder(ViewModelType.Companion::deriveTypeFrom) + .Factory(testFragment).create(ViewModelType.Companion::deriveTypeFrom) .setLifecycleOwner(testFragment) .setLifecycleOwner(testFragment) .build() @@ -391,8 +403,9 @@ class BindableAdapterTest { @Test fun testSingleTypeAdapter_withLiveData_noLifecycleOwner_doesNotRebindLiveDataValues() { + val testFragment = Fragment() // Set up the adapter to be used for this test. - TestModule.testAdapterFactory = { createSingleViewTypeWithDataBindingAndLiveDataAdapter() } + TestModule.testAdapterFactory = { createSingleViewTypeWithDataBindingAndLiveDataAdapter(testFragment) } launch(BindableAdapterTestActivity::class.java).use { scenario -> val itemLiveData = MutableLiveData("initial") @@ -444,8 +457,9 @@ class BindableAdapterTest { @Test fun testMultiTypeAdapter_withLiveData_noLifecycleOwner_doesNotRebindLiveDataValues() { + val testFragment = Fragment() // Set up the adapter to be used for this test. - TestModule.testAdapterFactory = { createMultiViewTypeWithDataBindingBindableAdapter() } + TestModule.testAdapterFactory = { createMultiViewTypeWithDataBindingBindableAdapter(testFragment) } launch(BindableAdapterTestActivity::class.java).use { scenario -> val itemLiveData = MutableLiveData("initial") @@ -499,10 +513,10 @@ class BindableAdapterTest { ApplicationProvider.getApplicationContext().inject(this) } - private fun createSingleViewTypeNoDataBindingBindableAdapter(): + private fun createSingleViewTypeNoDataBindingBindableAdapter(testFragment: Fragment): BindableAdapter { return SingleTypeBuilder - .newBuilder() + .Factory(testFragment).create() .registerViewBinder( inflateView = this::inflateTextViewForStringWithoutDataBinding, bindView = this::bindTextViewForStringWithoutDataBinding @@ -510,10 +524,10 @@ class BindableAdapterTest { .build() } - private fun createSingleViewTypeWithDataBindingBindableAdapter(): + private fun createSingleViewTypeWithDataBindingBindableAdapter(testFragment: Fragment): BindableAdapter { return SingleTypeBuilder - .newBuilder() + .Factory(testFragment).create() .registerViewDataBinderWithSameModelType( inflateDataBinding = TestTextViewForStringWithDataBindingBinding::inflate, setViewModel = TestTextViewForStringWithDataBindingBinding::setViewModel @@ -521,22 +535,11 @@ class BindableAdapterTest { .build() } - private fun createSingleViewTypeWithDataBindingAndLiveDataAdapter(): - BindableAdapter { - return SingleTypeBuilder - .newBuilder() - .registerViewDataBinderWithSameModelType( - inflateDataBinding = TestTextViewForLiveDataWithDataBindingBinding::inflate, - setViewModel = TestTextViewForLiveDataWithDataBindingBinding::setViewModel - ) - .build() - } - private fun createSingleViewTypeWithDataBindingAndLiveDataAdapter( lifecycleOwner: Fragment ): BindableAdapter { return SingleTypeBuilder - .newBuilder() + .Factory(lifecycleOwner).create() .setLifecycleOwner(lifecycleOwner) .registerViewDataBinderWithSameModelType( inflateDataBinding = TestTextViewForLiveDataWithDataBindingBinding::inflate, @@ -545,10 +548,10 @@ class BindableAdapterTest { .build() } - private fun createMultiViewTypeNoDataBindingBindableAdapter(): + private fun createMultiViewTypeNoDataBindingBindableAdapter(testFragment: Fragment): BindableAdapter { return MultiTypeBuilder - .newBuilder(ViewModelType.Companion::deriveTypeFrom) + .Factory(testFragment).create(ViewModelType.Companion::deriveTypeFrom) .registerViewBinder( viewType = ViewModelType.STRING, inflateView = this::inflateTextViewForStringWithoutDataBinding, @@ -562,33 +565,11 @@ class BindableAdapterTest { .build() } - private fun createMultiViewTypeWithDataBindingBindableAdapter(): - BindableAdapter { - return MultiTypeBuilder - .newBuilder(ViewModelType.Companion::deriveTypeFrom) - .registerViewDataBinderWithSameModelType( - viewType = ViewModelType.STRING, - inflateDataBinding = TestTextViewForStringWithDataBindingBinding::inflate, - setViewModel = TestTextViewForStringWithDataBindingBinding::setViewModel - ) - .registerViewDataBinderWithSameModelType( - viewType = ViewModelType.INT, - inflateDataBinding = TestTextViewForIntWithDataBindingBinding::inflate, - setViewModel = TestTextViewForIntWithDataBindingBinding::setViewModel - ) - .registerViewDataBinderWithSameModelType( - viewType = ViewModelType.LIVE_DATA, - inflateDataBinding = TestTextViewForLiveDataWithDataBindingBinding::inflate, - setViewModel = TestTextViewForLiveDataWithDataBindingBinding::setViewModel - ) - .build() - } - private fun createMultiViewTypeWithDataBindingBindableAdapter( lifecycleOwner: Fragment ): BindableAdapter { return MultiTypeBuilder - .newBuilder(ViewModelType.Companion::deriveTypeFrom) + .Factory(lifecycleOwner).create(ViewModelType.Companion::deriveTypeFrom) .setLifecycleOwner(lifecycleOwner) .registerViewDataBinderWithSameModelType( viewType = ViewModelType.STRING, From 955557cce25d042ba2497afb36c1fbdc28bb068c Mon Sep 17 00:00:00 2001 From: kevingitonga Date: Tue, 5 Jul 2022 17:11:37 +0300 Subject: [PATCH 05/34] Inject the BindableAdapter.kt factory into the fragment and listview instances. --- .../app/testing/DragDropTestFragment.kt | 57 +++++++++++++ .../testing/DragDropTestFragmentPresenter.kt | 80 +++++++++++++++++++ .../res/layout/drag_drop_test_fragment.xml | 10 +++ 3 files changed, 147 insertions(+) create mode 100644 app/src/main/java/org/oppia/android/app/testing/DragDropTestFragment.kt create mode 100644 app/src/main/java/org/oppia/android/app/testing/DragDropTestFragmentPresenter.kt create mode 100644 app/src/main/res/layout/drag_drop_test_fragment.xml diff --git a/app/src/main/java/org/oppia/android/app/testing/DragDropTestFragment.kt b/app/src/main/java/org/oppia/android/app/testing/DragDropTestFragment.kt new file mode 100644 index 00000000000..6f16e0d7675 --- /dev/null +++ b/app/src/main/java/org/oppia/android/app/testing/DragDropTestFragment.kt @@ -0,0 +1,57 @@ +package org.oppia.android.app.testing + +import android.content.Context +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import org.oppia.android.app.fragment.FragmentComponentImpl +import org.oppia.android.app.fragment.InjectableFragment +import org.oppia.android.app.recyclerview.OnDragEndedListener +import org.oppia.android.app.recyclerview.OnItemDragListener +import org.oppia.android.app.story.StoryFragment +import javax.inject.Inject + +/** Fragment for displaying a DragDropTestFragment. */ +class DragDropTestFragment : InjectableFragment(), OnItemDragListener, OnDragEndedListener { + + companion object { + /** Returns a new [StoryFragment] to display the story corresponding to the specified story ID. */ + fun newInstance(): DragDropTestFragment { + return DragDropTestFragment() + } + } + + @Inject + lateinit var dragDropTestFragmentPresenter: DragDropTestFragmentPresenter + + override fun onAttach(context: Context) { + super.onAttach(context) + (fragmentComponent as FragmentComponentImpl).inject(this) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + + return dragDropTestFragmentPresenter.handleCreateView( + inflater, + container + ) + } + + override fun onDragEnded(adapter: RecyclerView.Adapter) { + dragDropTestFragmentPresenter.onDragEnded(adapter) + } + + override fun onItemDragged( + indexFrom: Int, + indexTo: Int, + adapter: RecyclerView.Adapter + ) { + dragDropTestFragmentPresenter.onItemDragged(indexFrom, indexTo, adapter) + } +} diff --git a/app/src/main/java/org/oppia/android/app/testing/DragDropTestFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/testing/DragDropTestFragmentPresenter.kt new file mode 100644 index 00000000000..6b125a2a419 --- /dev/null +++ b/app/src/main/java/org/oppia/android/app/testing/DragDropTestFragmentPresenter.kt @@ -0,0 +1,80 @@ +package org.oppia.android.app.testing + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.appcompat.app.AppCompatActivity +import androidx.fragment.app.Fragment +import androidx.recyclerview.widget.RecyclerView +import org.oppia.android.R +import org.oppia.android.app.recyclerview.BindableAdapter +import org.oppia.android.databinding.DragDropTestFragmentBinding +import org.oppia.android.domain.oppialogger.OppiaLogger +import javax.inject.Inject + +/** The presenter for [DragDropTestFragment]. */ +class DragDropTestFragmentPresenter @Inject constructor( + private val activity: AppCompatActivity, + private val fragment: Fragment, + private val oppiaLogger: OppiaLogger, +) { + + var dataList = mutableListOf("Item 1", "Item 2", "Item 3", "Item 4") + private lateinit var binding: DragDropTestFragmentBinding + + fun handleCreateView( + inflater: LayoutInflater, + container: ViewGroup? + ): View? { + + binding = DragDropTestFragmentBinding.inflate( + inflater, + container, + /* attachToRoot= */ false + ) + + binding.dragDropRecyclerView.apply { + adapter = createBindableAdapter() + (adapter as BindableAdapter<*>).setDataUnchecked(dataList) + } + + return binding.root + } + + private fun createBindableAdapter(): BindableAdapter { + return BindableAdapter.SingleTypeBuilder + .Factory(fragment).create() + .registerViewBinder( + inflateView = this::inflateTextViewForStringWithoutDataBinding, + bindView = this::bindTextViewForStringWithoutDataBinding + ) + .build() + } + + private fun bindTextViewForStringWithoutDataBinding(textView: TextView, data: String) { + textView.text = data + } + + private fun inflateTextViewForStringWithoutDataBinding(viewGroup: ViewGroup): TextView { + val inflater = LayoutInflater.from(activity) + return inflater.inflate( + R.layout.test_text_view_for_string_no_data_binding, viewGroup, /* attachToRoot= */ false + ) as TextView + } + + fun onItemDragged( + indexFrom: Int, + indexTo: Int, + adapter: RecyclerView.Adapter + ) { + val item = dataList[indexFrom] + dataList.removeAt(indexFrom) + dataList.add(indexTo, item) + adapter.notifyItemMoved(indexFrom, indexTo) + } + + fun onDragEnded(adapter: RecyclerView.Adapter) { + (adapter as BindableAdapter<*>).setDataUnchecked(dataList) + } +} diff --git a/app/src/main/res/layout/drag_drop_test_fragment.xml b/app/src/main/res/layout/drag_drop_test_fragment.xml new file mode 100644 index 00000000000..34521233b2e --- /dev/null +++ b/app/src/main/res/layout/drag_drop_test_fragment.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file From 455f673ba7ef0bc60c0137bd0f0caca3757ebcc2 Mon Sep 17 00:00:00 2001 From: kevingitonga Date: Tue, 5 Jul 2022 17:11:50 +0300 Subject: [PATCH 06/34] Inject the BindableAdapter.kt factory into the fragment and listview instances. --- .../app/testing/DragDropTestFragment.kt | 57 ------------- .../testing/DragDropTestFragmentPresenter.kt | 80 ------------------- .../res/layout/drag_drop_test_fragment.xml | 10 --- 3 files changed, 147 deletions(-) delete mode 100644 app/src/main/java/org/oppia/android/app/testing/DragDropTestFragment.kt delete mode 100644 app/src/main/java/org/oppia/android/app/testing/DragDropTestFragmentPresenter.kt delete mode 100644 app/src/main/res/layout/drag_drop_test_fragment.xml diff --git a/app/src/main/java/org/oppia/android/app/testing/DragDropTestFragment.kt b/app/src/main/java/org/oppia/android/app/testing/DragDropTestFragment.kt deleted file mode 100644 index 6f16e0d7675..00000000000 --- a/app/src/main/java/org/oppia/android/app/testing/DragDropTestFragment.kt +++ /dev/null @@ -1,57 +0,0 @@ -package org.oppia.android.app.testing - -import android.content.Context -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.recyclerview.widget.RecyclerView -import org.oppia.android.app.fragment.FragmentComponentImpl -import org.oppia.android.app.fragment.InjectableFragment -import org.oppia.android.app.recyclerview.OnDragEndedListener -import org.oppia.android.app.recyclerview.OnItemDragListener -import org.oppia.android.app.story.StoryFragment -import javax.inject.Inject - -/** Fragment for displaying a DragDropTestFragment. */ -class DragDropTestFragment : InjectableFragment(), OnItemDragListener, OnDragEndedListener { - - companion object { - /** Returns a new [StoryFragment] to display the story corresponding to the specified story ID. */ - fun newInstance(): DragDropTestFragment { - return DragDropTestFragment() - } - } - - @Inject - lateinit var dragDropTestFragmentPresenter: DragDropTestFragmentPresenter - - override fun onAttach(context: Context) { - super.onAttach(context) - (fragmentComponent as FragmentComponentImpl).inject(this) - } - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - - return dragDropTestFragmentPresenter.handleCreateView( - inflater, - container - ) - } - - override fun onDragEnded(adapter: RecyclerView.Adapter) { - dragDropTestFragmentPresenter.onDragEnded(adapter) - } - - override fun onItemDragged( - indexFrom: Int, - indexTo: Int, - adapter: RecyclerView.Adapter - ) { - dragDropTestFragmentPresenter.onItemDragged(indexFrom, indexTo, adapter) - } -} diff --git a/app/src/main/java/org/oppia/android/app/testing/DragDropTestFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/testing/DragDropTestFragmentPresenter.kt deleted file mode 100644 index 6b125a2a419..00000000000 --- a/app/src/main/java/org/oppia/android/app/testing/DragDropTestFragmentPresenter.kt +++ /dev/null @@ -1,80 +0,0 @@ -package org.oppia.android.app.testing - -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.TextView -import androidx.appcompat.app.AppCompatActivity -import androidx.fragment.app.Fragment -import androidx.recyclerview.widget.RecyclerView -import org.oppia.android.R -import org.oppia.android.app.recyclerview.BindableAdapter -import org.oppia.android.databinding.DragDropTestFragmentBinding -import org.oppia.android.domain.oppialogger.OppiaLogger -import javax.inject.Inject - -/** The presenter for [DragDropTestFragment]. */ -class DragDropTestFragmentPresenter @Inject constructor( - private val activity: AppCompatActivity, - private val fragment: Fragment, - private val oppiaLogger: OppiaLogger, -) { - - var dataList = mutableListOf("Item 1", "Item 2", "Item 3", "Item 4") - private lateinit var binding: DragDropTestFragmentBinding - - fun handleCreateView( - inflater: LayoutInflater, - container: ViewGroup? - ): View? { - - binding = DragDropTestFragmentBinding.inflate( - inflater, - container, - /* attachToRoot= */ false - ) - - binding.dragDropRecyclerView.apply { - adapter = createBindableAdapter() - (adapter as BindableAdapter<*>).setDataUnchecked(dataList) - } - - return binding.root - } - - private fun createBindableAdapter(): BindableAdapter { - return BindableAdapter.SingleTypeBuilder - .Factory(fragment).create() - .registerViewBinder( - inflateView = this::inflateTextViewForStringWithoutDataBinding, - bindView = this::bindTextViewForStringWithoutDataBinding - ) - .build() - } - - private fun bindTextViewForStringWithoutDataBinding(textView: TextView, data: String) { - textView.text = data - } - - private fun inflateTextViewForStringWithoutDataBinding(viewGroup: ViewGroup): TextView { - val inflater = LayoutInflater.from(activity) - return inflater.inflate( - R.layout.test_text_view_for_string_no_data_binding, viewGroup, /* attachToRoot= */ false - ) as TextView - } - - fun onItemDragged( - indexFrom: Int, - indexTo: Int, - adapter: RecyclerView.Adapter - ) { - val item = dataList[indexFrom] - dataList.removeAt(indexFrom) - dataList.add(indexTo, item) - adapter.notifyItemMoved(indexFrom, indexTo) - } - - fun onDragEnded(adapter: RecyclerView.Adapter) { - (adapter as BindableAdapter<*>).setDataUnchecked(dataList) - } -} diff --git a/app/src/main/res/layout/drag_drop_test_fragment.xml b/app/src/main/res/layout/drag_drop_test_fragment.xml deleted file mode 100644 index 34521233b2e..00000000000 --- a/app/src/main/res/layout/drag_drop_test_fragment.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - \ No newline at end of file From 8a5d74f15a085b77762c952c029c1ee1f491df62 Mon Sep 17 00:00:00 2001 From: kevingitonga Date: Wed, 6 Jul 2022 11:53:40 +0300 Subject: [PATCH 07/34] Add 'androidx.work:work-testing:2.4.0' in androidTestImplementation to fix tests. --- app/build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/app/build.gradle b/app/build.gradle index 3900e0b93a6..af0c490cec8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -208,6 +208,7 @@ dependencies { 'androidx.test.ext:junit:1.1.1', 'com.github.bumptech.glide:mocks:4.11.0', 'com.google.truth:truth:1.1.3', + 'androidx.work:work-testing:2.4.0', 'com.google.truth.extensions:truth-liteproto-extension:1.1.3', 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.2.2', 'org.mockito:mockito-android:2.7.22', From e75cf57216e34780468616625665ebebc5019a6f Mon Sep 17 00:00:00 2001 From: kevingitonga Date: Wed, 6 Jul 2022 12:06:43 +0300 Subject: [PATCH 08/34] Fix lint formatting issue in updated files. --- .../ProfileAndDeviceIdFragment.kt | 1 - .../ProfileAndDeviceIdFragmentPresenter.kt | 17 +++++---- .../CompletedStoryListFragmentPresenter.kt | 2 +- .../DeveloperOptionsFragmentPresenter.kt | 38 +++++++++---------- .../ForceNetworkTypeFragmentPresenter.kt | 2 +- .../MarkChaptersCompletedFragmentPresenter.kt | 15 ++++---- .../MarkStoriesCompletedFragmentPresenter.kt | 2 +- .../MarkTopicsCompletedFragmentPresenter.kt | 2 +- .../ViewEventLogsFragmentPresenter.kt | 2 +- .../android/app/help/HelpFragmentPresenter.kt | 2 +- .../app/help/faq/FAQListFragmentPresenter.kt | 12 +++--- .../LicenseListFragmentPresenter.kt | 2 +- ...irdPartyDependencyListFragmentPresenter.kt | 2 +- ...HintsAndSolutionDialogFragmentPresenter.kt | 14 +++---- .../android/app/home/HomeFragmentPresenter.kt | 18 ++++----- .../promotedlist/ComingSoonTopicsListView.kt | 2 +- .../promotedlist/PromotedStoryListView.kt | 2 +- .../onboarding/OnboardingFragmentPresenter.kt | 12 +++--- .../OngoingTopicListFragmentPresenter.kt | 2 +- .../options/AudioLanguageFragmentPresenter.kt | 2 +- .../app/options/OptionsFragmentPresenter.kt | 32 ++++++++-------- .../ReadingTextSizeFragmentPresenter.kt | 2 +- .../state/DragDropSortInteractionView.kt | 2 +- .../player/state/SelectionInteractionView.kt | 2 +- .../ProfileChooserFragmentPresenter.kt | 9 +++-- .../ProfileProgressFragmentPresenter.kt | 12 +++--- .../app/recyclerview/BindableAdapter.kt | 1 - .../profile/ProfileListFragmentPresenter.kt | 2 +- .../app/story/StoryFragmentPresenter.kt | 12 +++--- .../lessons/TopicLessonsFragmentPresenter.kt | 12 +++--- .../TopicPracticeFragmentPresenter.kt | 14 +++---- .../TopicRevisionFragmentPresenter.kt | 2 +- .../WalkthroughTopicListFragmentPresenter.kt | 10 ++--- .../app/recyclerview/BindableAdapterTest.kt | 35 ++++++++++------- 34 files changed, 154 insertions(+), 144 deletions(-) diff --git a/app/src/main/java/org/oppia/android/app/administratorcontrols/learneranalytics/ProfileAndDeviceIdFragment.kt b/app/src/main/java/org/oppia/android/app/administratorcontrols/learneranalytics/ProfileAndDeviceIdFragment.kt index 00901f64159..6bc0ad70c56 100644 --- a/app/src/main/java/org/oppia/android/app/administratorcontrols/learneranalytics/ProfileAndDeviceIdFragment.kt +++ b/app/src/main/java/org/oppia/android/app/administratorcontrols/learneranalytics/ProfileAndDeviceIdFragment.kt @@ -18,7 +18,6 @@ class ProfileAndDeviceIdFragment : InjectableFragment() { @Inject lateinit var profileAndDeviceIdFragmentPresenter: ProfileAndDeviceIdFragmentPresenter - override fun onAttach(context: Context) { super.onAttach(context) (fragmentComponent as FragmentComponentImpl).inject(this) diff --git a/app/src/main/java/org/oppia/android/app/administratorcontrols/learneranalytics/ProfileAndDeviceIdFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/administratorcontrols/learneranalytics/ProfileAndDeviceIdFragmentPresenter.kt index d395dc57e00..b0d18d918c3 100644 --- a/app/src/main/java/org/oppia/android/app/administratorcontrols/learneranalytics/ProfileAndDeviceIdFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/administratorcontrols/learneranalytics/ProfileAndDeviceIdFragmentPresenter.kt @@ -17,7 +17,7 @@ import javax.inject.Inject class ProfileAndDeviceIdFragmentPresenter @Inject constructor( private val fragment: Fragment, private val profileListViewModelFactory: ProfileListViewModel.Factory, - private val multiTypeBuilderFactory:BindableAdapter.MultiTypeBuilder.Factory + private val multiTypeBuilderFactory: BindableAdapter.MultiTypeBuilder.Factory ) { private lateinit var binding: ProfileAndDeviceIdFragmentBinding @@ -39,14 +39,15 @@ class ProfileAndDeviceIdFragmentPresenter @Inject constructor( } private fun createRecyclerViewAdapter(): BindableAdapter { - return multiTypeBuilderFactory.create { viewModel -> - when (viewModel) { - is DeviceIdItemViewModel -> ProfileListItemViewType.DEVICE_ID - is ProfileLearnerIdItemViewModel -> ProfileListItemViewType.LEARNER_ID - is SyncStatusItemViewModel -> ProfileListItemViewType.SYNC_STATUS - else -> error("Encountered unexpected view model: $viewModel") - } + return multiTypeBuilderFactory.create { viewModel -> + when (viewModel) { + is DeviceIdItemViewModel -> ProfileListItemViewType.DEVICE_ID + is ProfileLearnerIdItemViewModel -> ProfileListItemViewType.LEARNER_ID + is SyncStatusItemViewModel -> ProfileListItemViewType.SYNC_STATUS + else -> error("Encountered unexpected view model: $viewModel") } + } .registerViewDataBinder( viewType = ProfileListItemViewType.DEVICE_ID, inflateDataBinding = ProfileListDeviceIdItemBinding::inflate, diff --git a/app/src/main/java/org/oppia/android/app/completedstorylist/CompletedStoryListFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/completedstorylist/CompletedStoryListFragmentPresenter.kt index 7894e26f199..0d6dd2eb221 100644 --- a/app/src/main/java/org/oppia/android/app/completedstorylist/CompletedStoryListFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/completedstorylist/CompletedStoryListFragmentPresenter.kt @@ -18,7 +18,7 @@ class CompletedStoryListFragmentPresenter @Inject constructor( private val activity: AppCompatActivity, private val fragment: Fragment, private val viewModelProvider: ViewModelProvider, - private val singleTypeBuilderFactory:BindableAdapter.SingleTypeBuilder.Factory + private val singleTypeBuilderFactory: BindableAdapter.SingleTypeBuilder.Factory ) { private lateinit var binding: CompletedStoryListFragmentBinding diff --git a/app/src/main/java/org/oppia/android/app/devoptions/DeveloperOptionsFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/devoptions/DeveloperOptionsFragmentPresenter.kt index c0bcfbec803..2f53aef47e3 100644 --- a/app/src/main/java/org/oppia/android/app/devoptions/DeveloperOptionsFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/devoptions/DeveloperOptionsFragmentPresenter.kt @@ -25,7 +25,7 @@ import javax.inject.Inject class DeveloperOptionsFragmentPresenter @Inject constructor( private val activity: AppCompatActivity, private val fragment: Fragment, - private val multiTypeBuilderFactory:BindableAdapter.MultiTypeBuilder.Factory + private val multiTypeBuilderFactory: BindableAdapter.MultiTypeBuilder.Factory ) { @@ -62,26 +62,26 @@ class DeveloperOptionsFragmentPresenter @Inject constructor( private fun createRecyclerViewAdapter(): BindableAdapter { return multiTypeBuilderFactory.create { viewModel -> - when (viewModel) { - is DeveloperOptionsModifyLessonProgressViewModel -> { - viewModel.itemIndex.set(0) - ViewType.VIEW_TYPE_MODIFY_LESSON_PROGRESS - } - is DeveloperOptionsViewLogsViewModel -> { - viewModel.itemIndex.set(1) - ViewType.VIEW_TYPE_VIEW_LOGS - } - is DeveloperOptionsOverrideAppBehaviorsViewModel -> { - viewModel.itemIndex.set(2) - ViewType.VIEW_TYPE_OVERRIDE_APP_BEHAVIORS - } - is DeveloperOptionsTestParsersViewModel -> { - viewModel.itemIndex.set(3) - ViewType.VIEW_TYPE_TEST_PARSERS - } - else -> throw IllegalArgumentException("Encountered unexpected view model: $viewModel") + when (viewModel) { + is DeveloperOptionsModifyLessonProgressViewModel -> { + viewModel.itemIndex.set(0) + ViewType.VIEW_TYPE_MODIFY_LESSON_PROGRESS } + is DeveloperOptionsViewLogsViewModel -> { + viewModel.itemIndex.set(1) + ViewType.VIEW_TYPE_VIEW_LOGS + } + is DeveloperOptionsOverrideAppBehaviorsViewModel -> { + viewModel.itemIndex.set(2) + ViewType.VIEW_TYPE_OVERRIDE_APP_BEHAVIORS + } + is DeveloperOptionsTestParsersViewModel -> { + viewModel.itemIndex.set(3) + ViewType.VIEW_TYPE_TEST_PARSERS + } + else -> throw IllegalArgumentException("Encountered unexpected view model: $viewModel") } + } .registerViewDataBinder( viewType = ViewType.VIEW_TYPE_MODIFY_LESSON_PROGRESS, inflateDataBinding = DeveloperOptionsModifyLessonProgressViewBinding::inflate, diff --git a/app/src/main/java/org/oppia/android/app/devoptions/forcenetworktype/ForceNetworkTypeFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/devoptions/forcenetworktype/ForceNetworkTypeFragmentPresenter.kt index 12c072e4fb1..1b09b469bb4 100644 --- a/app/src/main/java/org/oppia/android/app/devoptions/forcenetworktype/ForceNetworkTypeFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/devoptions/forcenetworktype/ForceNetworkTypeFragmentPresenter.kt @@ -22,7 +22,7 @@ class ForceNetworkTypeFragmentPresenter @Inject constructor( private val fragment: Fragment, private val networkConnectionUtil: Optional, private val viewModelProvider: ViewModelProvider, - private val singleTypeBuilderFactory:BindableAdapter.SingleTypeBuilder.Factory + private val singleTypeBuilderFactory: BindableAdapter.SingleTypeBuilder.Factory ) { private lateinit var binding: ForceNetworkTypeFragmentBinding diff --git a/app/src/main/java/org/oppia/android/app/devoptions/markchapterscompleted/MarkChaptersCompletedFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/devoptions/markchapterscompleted/MarkChaptersCompletedFragmentPresenter.kt index b8579e42b57..fee0646f45f 100644 --- a/app/src/main/java/org/oppia/android/app/devoptions/markchapterscompleted/MarkChaptersCompletedFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/devoptions/markchapterscompleted/MarkChaptersCompletedFragmentPresenter.kt @@ -24,7 +24,7 @@ class MarkChaptersCompletedFragmentPresenter @Inject constructor( private val fragment: Fragment, private val viewModelProvider: ViewModelProvider, private val modifyLessonProgressController: ModifyLessonProgressController, - private val multiTypeBuilderFactory:BindableAdapter.MultiTypeBuilder.Factory + private val multiTypeBuilderFactory: BindableAdapter.MultiTypeBuilder.Factory ) : ChapterSelector { private lateinit var binding: MarkChaptersCompletedFragmentBinding private lateinit var linearLayoutManager: LinearLayoutManager @@ -95,13 +95,14 @@ class MarkChaptersCompletedFragmentPresenter @Inject constructor( } private fun createRecyclerViewAdapter(): BindableAdapter { - return multiTypeBuilderFactory.create { viewModel -> - when (viewModel) { - is StorySummaryViewModel -> ViewType.VIEW_TYPE_STORY - is ChapterSummaryViewModel -> ViewType.VIEW_TYPE_CHAPTER - else -> throw IllegalArgumentException("Encountered unexpected view model: $viewModel") - } + return multiTypeBuilderFactory.create { viewModel -> + when (viewModel) { + is StorySummaryViewModel -> ViewType.VIEW_TYPE_STORY + is ChapterSummaryViewModel -> ViewType.VIEW_TYPE_CHAPTER + else -> throw IllegalArgumentException("Encountered unexpected view model: $viewModel") } + } .registerViewDataBinder( viewType = ViewType.VIEW_TYPE_STORY, inflateDataBinding = MarkChaptersCompletedStorySummaryViewBinding::inflate, diff --git a/app/src/main/java/org/oppia/android/app/devoptions/markstoriescompleted/MarkStoriesCompletedFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/devoptions/markstoriescompleted/MarkStoriesCompletedFragmentPresenter.kt index eb62c5ef750..9dcb77a41bd 100644 --- a/app/src/main/java/org/oppia/android/app/devoptions/markstoriescompleted/MarkStoriesCompletedFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/devoptions/markstoriescompleted/MarkStoriesCompletedFragmentPresenter.kt @@ -22,7 +22,7 @@ class MarkStoriesCompletedFragmentPresenter @Inject constructor( private val fragment: Fragment, private val viewModelProvider: ViewModelProvider, private val modifyLessonProgressController: ModifyLessonProgressController, - private val singleTypeBuilderFactory:BindableAdapter.SingleTypeBuilder.Factory + private val singleTypeBuilderFactory: BindableAdapter.SingleTypeBuilder.Factory ) : StorySelector { private lateinit var binding: MarkStoriesCompletedFragmentBinding private lateinit var linearLayoutManager: LinearLayoutManager diff --git a/app/src/main/java/org/oppia/android/app/devoptions/marktopicscompleted/MarkTopicsCompletedFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/devoptions/marktopicscompleted/MarkTopicsCompletedFragmentPresenter.kt index de73f50da00..fb4127c3450 100644 --- a/app/src/main/java/org/oppia/android/app/devoptions/marktopicscompleted/MarkTopicsCompletedFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/devoptions/marktopicscompleted/MarkTopicsCompletedFragmentPresenter.kt @@ -22,7 +22,7 @@ class MarkTopicsCompletedFragmentPresenter @Inject constructor( private val fragment: Fragment, private val viewModelProvider: ViewModelProvider, private val modifyLessonProgressController: ModifyLessonProgressController, - private val singleTypeAdapterFactory:BindableAdapter.SingleTypeBuilder.Factory + private val singleTypeAdapterFactory: BindableAdapter.SingleTypeBuilder.Factory ) : TopicSelector { private lateinit var binding: MarkTopicsCompletedFragmentBinding private lateinit var linearLayoutManager: LinearLayoutManager diff --git a/app/src/main/java/org/oppia/android/app/devoptions/vieweventlogs/ViewEventLogsFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/devoptions/vieweventlogs/ViewEventLogsFragmentPresenter.kt index 6c18bd4e4d3..6f0222142e7 100644 --- a/app/src/main/java/org/oppia/android/app/devoptions/vieweventlogs/ViewEventLogsFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/devoptions/vieweventlogs/ViewEventLogsFragmentPresenter.kt @@ -19,7 +19,7 @@ class ViewEventLogsFragmentPresenter @Inject constructor( private val activity: AppCompatActivity, private val fragment: Fragment, private val viewModelProvider: ViewModelProvider, - private val singleTypeAdapterFactory:BindableAdapter.SingleTypeBuilder.Factory + private val singleTypeAdapterFactory: BindableAdapter.SingleTypeBuilder.Factory ) { private lateinit var binding: ViewEventLogsFragmentBinding diff --git a/app/src/main/java/org/oppia/android/app/help/HelpFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/help/HelpFragmentPresenter.kt index 74d0efc0ccf..2748b323c4f 100644 --- a/app/src/main/java/org/oppia/android/app/help/HelpFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/help/HelpFragmentPresenter.kt @@ -19,7 +19,7 @@ class HelpFragmentPresenter @Inject constructor( private val activity: AppCompatActivity, private val fragment: Fragment, private val viewModelProvider: ViewModelProvider, - private val singleTypeBuilderFactory:BindableAdapter.SingleTypeBuilder.Factory + private val singleTypeBuilderFactory: BindableAdapter.SingleTypeBuilder.Factory ) { private lateinit var binding: HelpFragmentBinding diff --git a/app/src/main/java/org/oppia/android/app/help/faq/FAQListFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/help/faq/FAQListFragmentPresenter.kt index 1f7c525bc3f..ca0288a22bd 100644 --- a/app/src/main/java/org/oppia/android/app/help/faq/FAQListFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/help/faq/FAQListFragmentPresenter.kt @@ -23,7 +23,7 @@ class FAQListFragmentPresenter @Inject constructor( private val activity: AppCompatActivity, private val fragment: Fragment, private val viewModelProvider: ViewModelProvider, - private val multiTypeBuilderFactory:BindableAdapter.MultiTypeBuilder.Factory + private val multiTypeBuilderFactory: BindableAdapter.MultiTypeBuilder.Factory ) { private lateinit var binding: FaqListFragmentBinding @@ -50,12 +50,12 @@ class FAQListFragmentPresenter @Inject constructor( private fun createRecyclerViewAdapter(): BindableAdapter { return multiTypeBuilderFactory.create { viewModel -> - when (viewModel) { - is FAQHeaderViewModel -> ViewType.VIEW_TYPE_HEADER - is FAQContentViewModel -> ViewType.VIEW_TYPE_CONTENT - else -> throw IllegalArgumentException("Encountered unexpected view model: $viewModel") - } + when (viewModel) { + is FAQHeaderViewModel -> ViewType.VIEW_TYPE_HEADER + is FAQContentViewModel -> ViewType.VIEW_TYPE_CONTENT + else -> throw IllegalArgumentException("Encountered unexpected view model: $viewModel") } + } .registerViewDataBinder( viewType = ViewType.VIEW_TYPE_HEADER, inflateDataBinding = FaqItemHeaderBinding::inflate, diff --git a/app/src/main/java/org/oppia/android/app/help/thirdparty/LicenseListFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/help/thirdparty/LicenseListFragmentPresenter.kt index 3bc5ed77cc3..2970d448908 100644 --- a/app/src/main/java/org/oppia/android/app/help/thirdparty/LicenseListFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/help/thirdparty/LicenseListFragmentPresenter.kt @@ -19,7 +19,7 @@ class LicenseListFragmentPresenter @Inject constructor( private val activity: AppCompatActivity, private val fragment: Fragment, private val resourceHandler: AppLanguageResourceHandler, - private val singleTypeBuilderFactory:BindableAdapter.SingleTypeBuilder.Factory + private val singleTypeBuilderFactory: BindableAdapter.SingleTypeBuilder.Factory ) { private lateinit var binding: LicenseListFragmentBinding diff --git a/app/src/main/java/org/oppia/android/app/help/thirdparty/ThirdPartyDependencyListFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/help/thirdparty/ThirdPartyDependencyListFragmentPresenter.kt index 60101d15cde..50fa6374c40 100644 --- a/app/src/main/java/org/oppia/android/app/help/thirdparty/ThirdPartyDependencyListFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/help/thirdparty/ThirdPartyDependencyListFragmentPresenter.kt @@ -19,7 +19,7 @@ class ThirdPartyDependencyListFragmentPresenter @Inject constructor( private val activity: AppCompatActivity, private val fragment: Fragment, private val viewModelProvider: ViewModelProvider, - private val singleTypeBuilderFactory:BindableAdapter.SingleTypeBuilder.Factory + private val singleTypeBuilderFactory: BindableAdapter.SingleTypeBuilder.Factory ) { private lateinit var binding: ThirdPartyDependencyListFragmentBinding diff --git a/app/src/main/java/org/oppia/android/app/hintsandsolution/HintsAndSolutionDialogFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/hintsandsolution/HintsAndSolutionDialogFragmentPresenter.kt index 4cc57dd7893..f8aba56f4b6 100644 --- a/app/src/main/java/org/oppia/android/app/hintsandsolution/HintsAndSolutionDialogFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/hintsandsolution/HintsAndSolutionDialogFragmentPresenter.kt @@ -37,7 +37,7 @@ class HintsAndSolutionDialogFragmentPresenter @Inject constructor( @DefaultResourceBucketName private val resourceBucketName: String, @ExplorationHtmlParserEntityType private val entityType: String, private val resourceHandler: AppLanguageResourceHandler, - private val multiTypeAdapterFactory:BindableAdapter.MultiTypeBuilder.Factory + private val multiTypeAdapterFactory: BindableAdapter.MultiTypeBuilder.Factory ) { private var currentExpandedHintListIndex: Int? = null @@ -169,13 +169,13 @@ class HintsAndSolutionDialogFragmentPresenter @Inject constructor( private fun createRecyclerViewAdapter(): BindableAdapter { return multiTypeAdapterFactory.create { viewModel -> - when (viewModel) { - is HintsViewModel -> ViewType.VIEW_TYPE_HINT_ITEM - is SolutionViewModel -> ViewType.VIEW_TYPE_SOLUTION_ITEM - is HintsDividerViewModel -> ViewType.VIEW_TYPE_HINTS_DIVIDER_ITEM - else -> throw IllegalArgumentException("Encountered unexpected view model: $viewModel") - } + when (viewModel) { + is HintsViewModel -> ViewType.VIEW_TYPE_HINT_ITEM + is SolutionViewModel -> ViewType.VIEW_TYPE_SOLUTION_ITEM + is HintsDividerViewModel -> ViewType.VIEW_TYPE_HINTS_DIVIDER_ITEM + else -> throw IllegalArgumentException("Encountered unexpected view model: $viewModel") } + } .registerViewDataBinder( viewType = ViewType.VIEW_TYPE_HINT_ITEM, inflateDataBinding = HintsSummaryBinding::inflate, diff --git a/app/src/main/java/org/oppia/android/app/home/HomeFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/home/HomeFragmentPresenter.kt index 0d22e62ea01..cbf14a75538 100644 --- a/app/src/main/java/org/oppia/android/app/home/HomeFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/home/HomeFragmentPresenter.kt @@ -42,7 +42,7 @@ class HomeFragmentPresenter @Inject constructor( @StoryHtmlParserEntityType private val storyEntityType: String, private val resourceHandler: AppLanguageResourceHandler, private val dateTimeUtil: DateTimeUtil, - private val multiTypeAdapterFactory:BindableAdapter.MultiTypeBuilder.Factory + private val multiTypeAdapterFactory: BindableAdapter.MultiTypeBuilder.Factory ) { private val routeToTopicListener = activity as RouteToTopicListener private lateinit var binding: HomeFragmentBinding @@ -95,15 +95,15 @@ class HomeFragmentPresenter @Inject constructor( private fun createRecyclerViewAdapter(): BindableAdapter { return multiTypeAdapterFactory.create { viewModel -> - when (viewModel) { - is WelcomeViewModel -> ViewType.WELCOME_MESSAGE - is PromotedStoryListViewModel -> ViewType.PROMOTED_STORY_LIST - is ComingSoonTopicListViewModel -> ViewType.COMING_SOON_TOPIC_LIST - is AllTopicsViewModel -> ViewType.ALL_TOPICS - is TopicSummaryViewModel -> ViewType.TOPIC_LIST - else -> throw IllegalArgumentException("Encountered unexpected view model: $viewModel") - } + when (viewModel) { + is WelcomeViewModel -> ViewType.WELCOME_MESSAGE + is PromotedStoryListViewModel -> ViewType.PROMOTED_STORY_LIST + is ComingSoonTopicListViewModel -> ViewType.COMING_SOON_TOPIC_LIST + is AllTopicsViewModel -> ViewType.ALL_TOPICS + is TopicSummaryViewModel -> ViewType.TOPIC_LIST + else -> throw IllegalArgumentException("Encountered unexpected view model: $viewModel") } + } .registerViewDataBinder( viewType = ViewType.WELCOME_MESSAGE, inflateDataBinding = WelcomeBinding::inflate, diff --git a/app/src/main/java/org/oppia/android/app/home/promotedlist/ComingSoonTopicsListView.kt b/app/src/main/java/org/oppia/android/app/home/promotedlist/ComingSoonTopicsListView.kt index 4e9cee12433..e386b94ebf5 100644 --- a/app/src/main/java/org/oppia/android/app/home/promotedlist/ComingSoonTopicsListView.kt +++ b/app/src/main/java/org/oppia/android/app/home/promotedlist/ComingSoonTopicsListView.kt @@ -36,7 +36,7 @@ class ComingSoonTopicsListView @JvmOverloads constructor( lateinit var fragment: Fragment @Inject - lateinit var singleTypeAdapterFactory:BindableAdapter.SingleTypeBuilder.Factory + lateinit var singleTypeAdapterFactory: BindableAdapter.SingleTypeBuilder.Factory override fun onAttachedToWindow() { super.onAttachedToWindow() diff --git a/app/src/main/java/org/oppia/android/app/home/promotedlist/PromotedStoryListView.kt b/app/src/main/java/org/oppia/android/app/home/promotedlist/PromotedStoryListView.kt index b877ca867a6..e02141e63c5 100644 --- a/app/src/main/java/org/oppia/android/app/home/promotedlist/PromotedStoryListView.kt +++ b/app/src/main/java/org/oppia/android/app/home/promotedlist/PromotedStoryListView.kt @@ -36,7 +36,7 @@ class PromotedStoryListView @JvmOverloads constructor( lateinit var fragment: Fragment @Inject - lateinit var singleTypeBuilderFactory:BindableAdapter.SingleTypeBuilder.Factory + lateinit var singleTypeBuilderFactory: BindableAdapter.SingleTypeBuilder.Factory override fun onAttachedToWindow() { super.onAttachedToWindow() diff --git a/app/src/main/java/org/oppia/android/app/onboarding/OnboardingFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/onboarding/OnboardingFragmentPresenter.kt index 55094934e92..609ae5a9ac4 100644 --- a/app/src/main/java/org/oppia/android/app/onboarding/OnboardingFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/onboarding/OnboardingFragmentPresenter.kt @@ -27,7 +27,7 @@ class OnboardingFragmentPresenter @Inject constructor( private val viewModelProvider: ViewModelProvider, private val viewModelProviderFinalSlide: ViewModelProvider, private val resourceHandler: AppLanguageResourceHandler, - private val multiTypeBuilderFactory:BindableAdapter.MultiTypeBuilder.Factory + private val multiTypeBuilderFactory: BindableAdapter.MultiTypeBuilder.Factory ) : OnboardingNavigationListener { private val dotsList = ArrayList() private lateinit var binding: OnboardingFragmentBinding @@ -97,12 +97,12 @@ class OnboardingFragmentPresenter @Inject constructor( private fun createViewPagerAdapter(): BindableAdapter { return multiTypeBuilderFactory.create { viewModel -> - when (viewModel) { - is OnboardingSlideViewModel -> ViewType.ONBOARDING_MIDDLE_SLIDE - is OnboardingSlideFinalViewModel -> ViewType.ONBOARDING_FINAL_SLIDE - else -> throw IllegalArgumentException("Encountered unexpected view model: $viewModel") - } + when (viewModel) { + is OnboardingSlideViewModel -> ViewType.ONBOARDING_MIDDLE_SLIDE + is OnboardingSlideFinalViewModel -> ViewType.ONBOARDING_FINAL_SLIDE + else -> throw IllegalArgumentException("Encountered unexpected view model: $viewModel") } + } .registerViewDataBinder( viewType = ViewType.ONBOARDING_MIDDLE_SLIDE, inflateDataBinding = OnboardingSlideBinding::inflate, diff --git a/app/src/main/java/org/oppia/android/app/ongoingtopiclist/OngoingTopicListFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/ongoingtopiclist/OngoingTopicListFragmentPresenter.kt index d2534868fdc..09968096fad 100644 --- a/app/src/main/java/org/oppia/android/app/ongoingtopiclist/OngoingTopicListFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/ongoingtopiclist/OngoingTopicListFragmentPresenter.kt @@ -18,7 +18,7 @@ class OngoingTopicListFragmentPresenter @Inject constructor( private val activity: AppCompatActivity, private val fragment: Fragment, private val viewModelProvider: ViewModelProvider, - private val singleTypeBuilderFactory:BindableAdapter.SingleTypeBuilder.Factory + private val singleTypeBuilderFactory: BindableAdapter.SingleTypeBuilder.Factory ) { private lateinit var binding: OngoingTopicListFragmentBinding diff --git a/app/src/main/java/org/oppia/android/app/options/AudioLanguageFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/options/AudioLanguageFragmentPresenter.kt index f891ee395d1..de64c710f08 100644 --- a/app/src/main/java/org/oppia/android/app/options/AudioLanguageFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/options/AudioLanguageFragmentPresenter.kt @@ -13,7 +13,7 @@ import javax.inject.Inject class AudioLanguageFragmentPresenter @Inject constructor( private val fragment: Fragment, private val languageSelectionViewModel: LanguageSelectionViewModel, - private val singleTypeBuilderFactory:BindableAdapter.SingleTypeBuilder.Factory + private val singleTypeBuilderFactory: BindableAdapter.SingleTypeBuilder.Factory ) { private lateinit var prefSummaryValue: String fun handleOnCreateView( diff --git a/app/src/main/java/org/oppia/android/app/options/OptionsFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/options/OptionsFragmentPresenter.kt index b0a64c9fd5e..29962861c8d 100644 --- a/app/src/main/java/org/oppia/android/app/options/OptionsFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/options/OptionsFragmentPresenter.kt @@ -47,7 +47,7 @@ class OptionsFragmentPresenter @Inject constructor( private val profileManagementController: ProfileManagementController, private val viewModelProvider: ViewModelProvider, private val oppiaLogger: OppiaLogger, - private val multiTypeBuilderFactory:BindableAdapter.MultiTypeBuilder.Factory + private val multiTypeBuilderFactory: BindableAdapter.MultiTypeBuilder.Factory ) { private lateinit var binding: OptionsFragmentBinding private lateinit var recyclerViewAdapter: RecyclerView.Adapter<*> @@ -96,23 +96,23 @@ class OptionsFragmentPresenter @Inject constructor( isMultipane: Boolean ): BindableAdapter { return multiTypeBuilderFactory.create { viewModel -> - viewModel.isMultipane.set(isMultipane) - when (viewModel) { - is OptionsReadingTextSizeViewModel -> { - viewModel.itemIndex.set(0) - ViewType.VIEW_TYPE_READING_TEXT_SIZE - } - is OptionsAppLanguageViewModel -> { - viewModel.itemIndex.set(1) - ViewType.VIEW_TYPE_APP_LANGUAGE - } - is OptionsAudioLanguageViewModel -> { - viewModel.itemIndex.set(2) - ViewType.VIEW_TYPE_AUDIO_LANGUAGE - } - else -> throw IllegalArgumentException("Encountered unexpected view model: $viewModel") + viewModel.isMultipane.set(isMultipane) + when (viewModel) { + is OptionsReadingTextSizeViewModel -> { + viewModel.itemIndex.set(0) + ViewType.VIEW_TYPE_READING_TEXT_SIZE } + is OptionsAppLanguageViewModel -> { + viewModel.itemIndex.set(1) + ViewType.VIEW_TYPE_APP_LANGUAGE + } + is OptionsAudioLanguageViewModel -> { + viewModel.itemIndex.set(2) + ViewType.VIEW_TYPE_AUDIO_LANGUAGE + } + else -> throw IllegalArgumentException("Encountered unexpected view model: $viewModel") } + } .registerViewDataBinder( viewType = ViewType.VIEW_TYPE_READING_TEXT_SIZE, inflateDataBinding = OptionStoryTextSizeBinding::inflate, diff --git a/app/src/main/java/org/oppia/android/app/options/ReadingTextSizeFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/options/ReadingTextSizeFragmentPresenter.kt index 4bf0dd56eb9..99b0677f23f 100644 --- a/app/src/main/java/org/oppia/android/app/options/ReadingTextSizeFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/options/ReadingTextSizeFragmentPresenter.kt @@ -16,7 +16,7 @@ class ReadingTextSizeFragmentPresenter @Inject constructor( private val fragment: Fragment, private val readingTextSizeSelectionViewModel: ReadingTextSizeSelectionViewModel, resourceHandler: AppLanguageResourceHandler, - private val singleTypeBuilderFactory:BindableAdapter.SingleTypeBuilder.Factory + private val singleTypeBuilderFactory: BindableAdapter.SingleTypeBuilder.Factory ) { private var fontSize: String = resourceHandler.getStringInLocale( R.string.reading_text_size_medium diff --git a/app/src/main/java/org/oppia/android/app/player/state/DragDropSortInteractionView.kt b/app/src/main/java/org/oppia/android/app/player/state/DragDropSortInteractionView.kt index 745161ba8c8..7632ada52ae 100644 --- a/app/src/main/java/org/oppia/android/app/player/state/DragDropSortInteractionView.kt +++ b/app/src/main/java/org/oppia/android/app/player/state/DragDropSortInteractionView.kt @@ -57,7 +57,7 @@ class DragDropSortInteractionView @JvmOverloads constructor( lateinit var fragment: Fragment @Inject - lateinit var singleTypeBuilderFactory:BindableAdapter.SingleTypeBuilder.Factory + lateinit var singleTypeBuilderFactory: BindableAdapter.SingleTypeBuilder.Factory private lateinit var entityId: String private lateinit var onDragEnd: OnDragEndedListener diff --git a/app/src/main/java/org/oppia/android/app/player/state/SelectionInteractionView.kt b/app/src/main/java/org/oppia/android/app/player/state/SelectionInteractionView.kt index b4f6732b55c..d1785f22bdc 100644 --- a/app/src/main/java/org/oppia/android/app/player/state/SelectionInteractionView.kt +++ b/app/src/main/java/org/oppia/android/app/player/state/SelectionInteractionView.kt @@ -49,7 +49,7 @@ class SelectionInteractionView @JvmOverloads constructor( lateinit var fragment: Fragment @Inject - lateinit var singleTypeBuilderFactory:BindableAdapter.SingleTypeBuilder.Factory + lateinit var singleTypeBuilderFactory: BindableAdapter.SingleTypeBuilder.Factory private lateinit var entityId: String private lateinit var writtenTranslationContext: WrittenTranslationContext diff --git a/app/src/main/java/org/oppia/android/app/profile/ProfileChooserFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/profile/ProfileChooserFragmentPresenter.kt index 28b41c2adbf..5e71216c7a1 100644 --- a/app/src/main/java/org/oppia/android/app/profile/ProfileChooserFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/profile/ProfileChooserFragmentPresenter.kt @@ -66,7 +66,7 @@ class ProfileChooserFragmentPresenter @Inject constructor( private val viewModelProvider: ViewModelProvider, private val profileManagementController: ProfileManagementController, private val oppiaLogger: OppiaLogger, - private val multiTypeAdapterBuilder:BindableAdapter.MultiTypeBuilder.Factory + private val multiTypeAdapterBuilder: BindableAdapter.MultiTypeBuilder.Factory ) { private lateinit var binding: ProfileChooserFragmentBinding val hasProfileEverBeenAddedValue = ObservableField(true) @@ -149,9 +149,10 @@ class ProfileChooserFragmentPresenter @Inject constructor( } private fun createRecyclerViewAdapter(): BindableAdapter { - return multiTypeAdapterBuilder.create( - ProfileChooserUiModel::getModelTypeCase - ) + return multiTypeAdapterBuilder.create( + ProfileChooserUiModel::getModelTypeCase + ) .registerViewDataBinderWithSameModelType( viewType = ProfileChooserUiModel.ModelTypeCase.PROFILE, inflateDataBinding = ProfileChooserProfileViewBinding::inflate, diff --git a/app/src/main/java/org/oppia/android/app/profileprogress/ProfileProgressFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/profileprogress/ProfileProgressFragmentPresenter.kt index af6ecab10da..c449ee04532 100644 --- a/app/src/main/java/org/oppia/android/app/profileprogress/ProfileProgressFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/profileprogress/ProfileProgressFragmentPresenter.kt @@ -27,7 +27,7 @@ class ProfileProgressFragmentPresenter @Inject constructor( lateinit var viewModel: ProfileProgressViewModel @Inject - lateinit var multiTypeAdapterBuilder:BindableAdapter.MultiTypeBuilder.Factory + lateinit var multiTypeAdapterBuilder: BindableAdapter.MultiTypeBuilder.Factory fun handleCreateView( inflater: LayoutInflater, @@ -74,12 +74,12 @@ class ProfileProgressFragmentPresenter @Inject constructor( private fun createRecyclerViewAdapter(): BindableAdapter { return multiTypeAdapterBuilder.create { viewModel -> - when (viewModel) { - is ProfileProgressHeaderViewModel -> ViewType.VIEW_TYPE_HEADER - is RecentlyPlayedStorySummaryViewModel -> ViewType.VIEW_TYPE_RECENTLY_PLAYED_STORY - else -> throw IllegalArgumentException("Encountered unexpected view model: $viewModel") - } + when (viewModel) { + is ProfileProgressHeaderViewModel -> ViewType.VIEW_TYPE_HEADER + is RecentlyPlayedStorySummaryViewModel -> ViewType.VIEW_TYPE_RECENTLY_PLAYED_STORY + else -> throw IllegalArgumentException("Encountered unexpected view model: $viewModel") } + } .registerViewDataBinder( viewType = ViewType.VIEW_TYPE_HEADER, inflateDataBinding = ProfileProgressHeaderBinding::inflate, diff --git a/app/src/main/java/org/oppia/android/app/recyclerview/BindableAdapter.kt b/app/src/main/java/org/oppia/android/app/recyclerview/BindableAdapter.kt index 242a12b8558..5fa53d2d6f8 100644 --- a/app/src/main/java/org/oppia/android/app/recyclerview/BindableAdapter.kt +++ b/app/src/main/java/org/oppia/android/app/recyclerview/BindableAdapter.kt @@ -1,6 +1,5 @@ package org.oppia.android.app.recyclerview -import android.app.Activity import android.view.LayoutInflater import android.view.View import android.view.ViewGroup diff --git a/app/src/main/java/org/oppia/android/app/settings/profile/ProfileListFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/settings/profile/ProfileListFragmentPresenter.kt index e6ceeb4950d..064be19a51b 100644 --- a/app/src/main/java/org/oppia/android/app/settings/profile/ProfileListFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/settings/profile/ProfileListFragmentPresenter.kt @@ -20,7 +20,7 @@ class ProfileListFragmentPresenter @Inject constructor( private val activity: AppCompatActivity, private val fragment: Fragment, private val viewModelProvider: ViewModelProvider, - private val singleTypeAdapterBuilder:BindableAdapter.SingleTypeBuilder.Factory + private val singleTypeAdapterBuilder: BindableAdapter.SingleTypeBuilder.Factory ) { private var isMultipane = false diff --git a/app/src/main/java/org/oppia/android/app/story/StoryFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/story/StoryFragmentPresenter.kt index 9976baa3b01..bd76ea0a933 100644 --- a/app/src/main/java/org/oppia/android/app/story/StoryFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/story/StoryFragmentPresenter.kt @@ -50,7 +50,7 @@ class StoryFragmentPresenter @Inject constructor( @DefaultResourceBucketName private val resourceBucketName: String, @TopicHtmlParserEntityType private val entityType: String, private val resourceHandler: AppLanguageResourceHandler, - private val multiTypeAdapterFactory:BindableAdapter.MultiTypeBuilder.Factory + private val multiTypeAdapterFactory: BindableAdapter.MultiTypeBuilder.Factory ) { private val routeToExplorationListener = activity as RouteToExplorationListener private val routeToResumeLessonListener = activity as RouteToResumeLessonListener @@ -137,12 +137,12 @@ class StoryFragmentPresenter @Inject constructor( private fun createRecyclerViewAdapter(): BindableAdapter { return multiTypeAdapterFactory.create { viewModel -> - when (viewModel) { - is StoryHeaderViewModel -> ViewType.VIEW_TYPE_HEADER - is StoryChapterSummaryViewModel -> ViewType.VIEW_TYPE_CHAPTER - else -> throw IllegalArgumentException("Encountered unexpected view model: $viewModel") - } + when (viewModel) { + is StoryHeaderViewModel -> ViewType.VIEW_TYPE_HEADER + is StoryChapterSummaryViewModel -> ViewType.VIEW_TYPE_CHAPTER + else -> throw IllegalArgumentException("Encountered unexpected view model: $viewModel") } + } .registerViewDataBinder( viewType = ViewType.VIEW_TYPE_HEADER, inflateDataBinding = StoryHeaderViewBinding::inflate, diff --git a/app/src/main/java/org/oppia/android/app/topic/lessons/TopicLessonsFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/topic/lessons/TopicLessonsFragmentPresenter.kt index ff5bdd7f83c..ca5ec11b19f 100644 --- a/app/src/main/java/org/oppia/android/app/topic/lessons/TopicLessonsFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/topic/lessons/TopicLessonsFragmentPresenter.kt @@ -35,7 +35,7 @@ class TopicLessonsFragmentPresenter @Inject constructor( private val oppiaLogger: OppiaLogger, private val explorationDataController: ExplorationDataController, private val explorationCheckpointController: ExplorationCheckpointController, - private val multiTypeAdapterFactory:BindableAdapter.MultiTypeBuilder.Factory + private val multiTypeAdapterFactory: BindableAdapter.MultiTypeBuilder.Factory ) { private val routeToResumeLessonListener = activity as RouteToResumeLessonListener @@ -106,12 +106,12 @@ class TopicLessonsFragmentPresenter @Inject constructor( private fun createRecyclerViewAdapter(): BindableAdapter { return multiTypeAdapterFactory.create { viewModel -> - when (viewModel) { - is StorySummaryViewModel -> ViewType.VIEW_TYPE_STORY_ITEM - is TopicLessonsTitleViewModel -> ViewType.VIEW_TYPE_TITLE_TEXT - else -> throw IllegalArgumentException("Encountered unexpected view model: $viewModel") - } + when (viewModel) { + is StorySummaryViewModel -> ViewType.VIEW_TYPE_STORY_ITEM + is TopicLessonsTitleViewModel -> ViewType.VIEW_TYPE_TITLE_TEXT + else -> throw IllegalArgumentException("Encountered unexpected view model: $viewModel") } + } .registerViewBinder( viewType = ViewType.VIEW_TYPE_TITLE_TEXT, inflateView = { parent -> diff --git a/app/src/main/java/org/oppia/android/app/topic/practice/TopicPracticeFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/topic/practice/TopicPracticeFragmentPresenter.kt index 9622076cf45..ee596399a90 100644 --- a/app/src/main/java/org/oppia/android/app/topic/practice/TopicPracticeFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/topic/practice/TopicPracticeFragmentPresenter.kt @@ -28,7 +28,7 @@ class TopicPracticeFragmentPresenter @Inject constructor( private val fragment: Fragment, private val oppiaLogger: OppiaLogger, private val viewModelProvider: ViewModelProvider, - private val multiTypeAdapterFactory:BindableAdapter.MultiTypeBuilder.Factory + private val multiTypeAdapterFactory: BindableAdapter.MultiTypeBuilder.Factory ) : SubtopicSelector { private lateinit var binding: TopicPracticeFragmentBinding private lateinit var linearLayoutManager: LinearLayoutManager @@ -75,13 +75,13 @@ class TopicPracticeFragmentPresenter @Inject constructor( private fun createRecyclerViewAdapter(): BindableAdapter { return multiTypeAdapterFactory.create { viewModel -> - when (viewModel) { - is TopicPracticeHeaderViewModel -> ViewType.VIEW_TYPE_HEADER - is TopicPracticeSubtopicViewModel -> ViewType.VIEW_TYPE_SKILL - is TopicPracticeFooterViewModel -> ViewType.VIEW_TYPE_FOOTER - else -> throw IllegalArgumentException("Encountered unexpected view model: $viewModel") - } + when (viewModel) { + is TopicPracticeHeaderViewModel -> ViewType.VIEW_TYPE_HEADER + is TopicPracticeSubtopicViewModel -> ViewType.VIEW_TYPE_SKILL + is TopicPracticeFooterViewModel -> ViewType.VIEW_TYPE_FOOTER + else -> throw IllegalArgumentException("Encountered unexpected view model: $viewModel") } + } .registerViewDataBinder( viewType = ViewType.VIEW_TYPE_HEADER, inflateDataBinding = TopicPracticeHeaderViewBinding::inflate, diff --git a/app/src/main/java/org/oppia/android/app/topic/revision/TopicRevisionFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/topic/revision/TopicRevisionFragmentPresenter.kt index a9a0c8769c9..c795ec3e52d 100755 --- a/app/src/main/java/org/oppia/android/app/topic/revision/TopicRevisionFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/topic/revision/TopicRevisionFragmentPresenter.kt @@ -23,7 +23,7 @@ class TopicRevisionFragmentPresenter @Inject constructor( activity: AppCompatActivity, private val fragment: Fragment, private val viewModelProvider: ViewModelProvider, - private val singleTypeAdapterFactory:BindableAdapter.SingleTypeBuilder.Factory + private val singleTypeAdapterFactory: BindableAdapter.SingleTypeBuilder.Factory ) : RevisionSubtopicSelector { private lateinit var binding: TopicRevisionFragmentBinding private var internalProfileId: Int = -1 diff --git a/app/src/main/java/org/oppia/android/app/walkthrough/topiclist/WalkthroughTopicListFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/walkthrough/topiclist/WalkthroughTopicListFragmentPresenter.kt index 254d930da21..ba1a76d76a5 100644 --- a/app/src/main/java/org/oppia/android/app/walkthrough/topiclist/WalkthroughTopicListFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/walkthrough/topiclist/WalkthroughTopicListFragmentPresenter.kt @@ -75,12 +75,12 @@ class WalkthroughTopicListFragmentPresenter @Inject constructor( private fun createRecyclerViewAdapter(): BindableAdapter { return multiTypeAdapterFactory.create { viewModel -> - when (viewModel) { - is WalkthroughTopicHeaderViewModel -> ViewType.VIEW_TYPE_HEADER - is WalkthroughTopicSummaryViewModel -> ViewType.VIEW_TYPE_TOPIC - else -> throw IllegalArgumentException("Encountered unexpected view model: $viewModel") - } + when (viewModel) { + is WalkthroughTopicHeaderViewModel -> ViewType.VIEW_TYPE_HEADER + is WalkthroughTopicSummaryViewModel -> ViewType.VIEW_TYPE_TOPIC + else -> throw IllegalArgumentException("Encountered unexpected view model: $viewModel") } + } .registerViewDataBinder( viewType = ViewType.VIEW_TYPE_HEADER, inflateDataBinding = WalkthroughTopicHeaderViewBinding::inflate, diff --git a/app/src/sharedTest/java/org/oppia/android/app/recyclerview/BindableAdapterTest.kt b/app/src/sharedTest/java/org/oppia/android/app/recyclerview/BindableAdapterTest.kt index 1fdcb401f72..ef436215158 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/recyclerview/BindableAdapterTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/recyclerview/BindableAdapterTest.kt @@ -144,7 +144,6 @@ class BindableAdapterTest { @Inject lateinit var testCoroutineDispatchers: TestCoroutineDispatchers - @Before fun setUp() { setUpTestApplicationComponent() @@ -166,7 +165,8 @@ class BindableAdapterTest { fun testSingleTypeAdapter_withOneViewType_noData_bindsNoViews() { val testFragment = Fragment() // Set up the adapter to be used for this test. - TestModule.testAdapterFactory = { createSingleViewTypeNoDataBindingBindableAdapter(testFragment) } + TestModule.testAdapterFactory = + { createSingleViewTypeNoDataBindingBindableAdapter(testFragment) } launch(BindableAdapterTestActivity::class.java).use { scenario -> scenario.onActivity { activity -> @@ -182,7 +182,8 @@ class BindableAdapterTest { fun testSingleTypeAdapter_withOneViewType_setItem_automaticallyBindsView() { val testFragment = Fragment() // Set up the adapter to be used for this test. - TestModule.testAdapterFactory = { createSingleViewTypeNoDataBindingBindableAdapter(testFragment) } + TestModule.testAdapterFactory = + { createSingleViewTypeNoDataBindingBindableAdapter(testFragment) } launch(BindableAdapterTestActivity::class.java).use { scenario -> scenario.onActivity { activity -> @@ -206,7 +207,8 @@ class BindableAdapterTest { fun testSingleTypeAdapter_withOneViewType_nullData_bindsNoViews() { val testFragment = Fragment() // Set up the adapter to be used for this test. - TestModule.testAdapterFactory = { createSingleViewTypeNoDataBindingBindableAdapter(testFragment) } + TestModule.testAdapterFactory = + { createSingleViewTypeNoDataBindingBindableAdapter(testFragment) } launch(BindableAdapterTestActivity::class.java).use { scenario -> scenario.onActivity { activity -> @@ -221,7 +223,8 @@ class BindableAdapterTest { fun testSingleTypeAdapter_withOneViewType_setMultipleItems_automaticallyBinds() { val testFragment = Fragment() // Set up the adapter to be used for this test. - TestModule.testAdapterFactory = { createSingleViewTypeNoDataBindingBindableAdapter(testFragment) } + TestModule.testAdapterFactory = + { createSingleViewTypeNoDataBindingBindableAdapter(testFragment) } launch(BindableAdapterTestActivity::class.java).use { scenario -> scenario.onActivity { activity -> @@ -248,7 +251,8 @@ class BindableAdapterTest { fun testMultiTypeAdapter_withTwoViewTypes_setItems_autoBindsCorrectItemsPerTypes() { val testFragment = Fragment() // Set up the adapter to be used for this test. - TestModule.testAdapterFactory = { createMultiViewTypeNoDataBindingBindableAdapter(testFragment) } + TestModule.testAdapterFactory = + { createMultiViewTypeNoDataBindingBindableAdapter(testFragment) } launch(BindableAdapterTestActivity::class.java).use { scenario -> scenario.onActivity { activity -> @@ -285,7 +289,8 @@ class BindableAdapterTest { fun testSingleTypeAdapter_withOneDataBoundViewType_setItem_automaticallyBindsView() { val testFragment = Fragment() // Set up the adapter to be used for this test. - TestModule.testAdapterFactory = { createSingleViewTypeWithDataBindingBindableAdapter(testFragment) } + TestModule.testAdapterFactory = + { createSingleViewTypeWithDataBindingBindableAdapter(testFragment) } launch(BindableAdapterTestActivity::class.java).use { scenario -> scenario.onActivity { activity -> @@ -309,9 +314,10 @@ class BindableAdapterTest { fun testSingleTypeAdapter_withTwoDataBoundViewTypes_setItems_autoBindsCorrectItemsPerTypes() { val testFragment = Fragment() // Set up the adapter to be used for this test. - TestModule.testAdapterFactory = { createSingleViewTypeWithDataBindingBindableAdapter( - testFragment - ) + TestModule.testAdapterFactory = { + createSingleViewTypeWithDataBindingBindableAdapter( + testFragment + ) } launch(BindableAdapterTestActivity::class.java).use { scenario -> @@ -340,7 +346,8 @@ class BindableAdapterTest { fun testMultiTypeAdapter_partiallyDataBoundViewTypes_setItems_autoBindsCorrectItemsPerTypes() { val testFragment = Fragment() // Set up the adapter to be used for this test. - TestModule.testAdapterFactory = { createMultiViewTypeWithDataBindingBindableAdapter(testFragment) } + TestModule.testAdapterFactory = + { createMultiViewTypeWithDataBindingBindableAdapter(testFragment) } launch(BindableAdapterTestActivity::class.java).use { scenario -> scenario.onActivity { activity -> @@ -405,7 +412,8 @@ class BindableAdapterTest { fun testSingleTypeAdapter_withLiveData_noLifecycleOwner_doesNotRebindLiveDataValues() { val testFragment = Fragment() // Set up the adapter to be used for this test. - TestModule.testAdapterFactory = { createSingleViewTypeWithDataBindingAndLiveDataAdapter(testFragment) } + TestModule.testAdapterFactory = + { createSingleViewTypeWithDataBindingAndLiveDataAdapter(testFragment) } launch(BindableAdapterTestActivity::class.java).use { scenario -> val itemLiveData = MutableLiveData("initial") @@ -459,7 +467,8 @@ class BindableAdapterTest { fun testMultiTypeAdapter_withLiveData_noLifecycleOwner_doesNotRebindLiveDataValues() { val testFragment = Fragment() // Set up the adapter to be used for this test. - TestModule.testAdapterFactory = { createMultiViewTypeWithDataBindingBindableAdapter(testFragment) } + TestModule.testAdapterFactory = + { createMultiViewTypeWithDataBindingBindableAdapter(testFragment) } launch(BindableAdapterTestActivity::class.java).use { scenario -> val itemLiveData = MutableLiveData("initial") From 6d72ea98a7310f6e5152a7d942396c68f77df92b Mon Sep 17 00:00:00 2001 From: kevingitonga Date: Tue, 12 Jul 2022 15:52:34 +0300 Subject: [PATCH 09/34] Fix some of the failing tests after fixing #2658. --- .../learneranalytics/ProfileAndDeviceIdFragmentPresenter.kt | 2 +- .../oppia/android/app/options/AppLanguageFragmentPresenter.kt | 1 + .../android/app/options/ReadingTextSizeFragmentPresenter.kt | 3 ++- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/oppia/android/app/administratorcontrols/learneranalytics/ProfileAndDeviceIdFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/administratorcontrols/learneranalytics/ProfileAndDeviceIdFragmentPresenter.kt index b0d18d918c3..b57ef06559b 100644 --- a/app/src/main/java/org/oppia/android/app/administratorcontrols/learneranalytics/ProfileAndDeviceIdFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/administratorcontrols/learneranalytics/ProfileAndDeviceIdFragmentPresenter.kt @@ -47,7 +47,7 @@ class ProfileAndDeviceIdFragmentPresenter @Inject constructor( is SyncStatusItemViewModel -> ProfileListItemViewType.SYNC_STATUS else -> error("Encountered unexpected view model: $viewModel") } - } + }.setLifecycleOwner(fragment) .registerViewDataBinder( viewType = ProfileListItemViewType.DEVICE_ID, inflateDataBinding = ProfileListDeviceIdItemBinding::inflate, diff --git a/app/src/main/java/org/oppia/android/app/options/AppLanguageFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/options/AppLanguageFragmentPresenter.kt index 1433ae55098..ebadf3921a0 100644 --- a/app/src/main/java/org/oppia/android/app/options/AppLanguageFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/options/AppLanguageFragmentPresenter.kt @@ -43,6 +43,7 @@ class AppLanguageFragmentPresenter @Inject constructor( private fun createRecyclerViewAdapter(): BindableAdapter { return singleTypeBuilderFactory.create() + .setLifecycleOwner(fragment) .registerViewDataBinderWithSameModelType( inflateDataBinding = LanguageItemsBinding::inflate, setViewModel = LanguageItemsBinding::setViewModel diff --git a/app/src/main/java/org/oppia/android/app/options/ReadingTextSizeFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/options/ReadingTextSizeFragmentPresenter.kt index 99b0677f23f..9ea9259672a 100644 --- a/app/src/main/java/org/oppia/android/app/options/ReadingTextSizeFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/options/ReadingTextSizeFragmentPresenter.kt @@ -53,7 +53,8 @@ class ReadingTextSizeFragmentPresenter @Inject constructor( .registerViewDataBinderWithSameModelType( inflateDataBinding = TextSizeItemsBinding::inflate, setViewModel = TextSizeItemsBinding::setViewModel - ).build() + ).setLifecycleOwner(fragment) + .build() } private fun updateTextSize(textSize: String) { From e92b11327b253d902d24e4ae7d8fc87fa31afefd Mon Sep 17 00:00:00 2001 From: kevingitonga Date: Wed, 13 Jul 2022 11:27:36 +0300 Subject: [PATCH 10/34] Fix some of the failing tests after fixing #2658. --- .../learneranalytics/ProfileAndDeviceIdFragmentPresenter.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/org/oppia/android/app/administratorcontrols/learneranalytics/ProfileAndDeviceIdFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/administratorcontrols/learneranalytics/ProfileAndDeviceIdFragmentPresenter.kt index b57ef06559b..542e712a40f 100644 --- a/app/src/main/java/org/oppia/android/app/administratorcontrols/learneranalytics/ProfileAndDeviceIdFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/administratorcontrols/learneranalytics/ProfileAndDeviceIdFragmentPresenter.kt @@ -19,6 +19,7 @@ class ProfileAndDeviceIdFragmentPresenter @Inject constructor( private val profileListViewModelFactory: ProfileListViewModel.Factory, private val multiTypeBuilderFactory: BindableAdapter.MultiTypeBuilder.Factory ) { + private lateinit var binding: ProfileAndDeviceIdFragmentBinding /** Handles [ProfileAndDeviceIdFragment]'s creation flow. */ From 9e1bec04b912d91daa58c1e82a0c5d15cd859564 Mon Sep 17 00:00:00 2001 From: kevingitonga Date: Wed, 13 Jul 2022 13:50:32 +0300 Subject: [PATCH 11/34] Fix more failing tests for #2658. --- .../oppia/android/app/options/AudioLanguageFragmentPresenter.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/org/oppia/android/app/options/AudioLanguageFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/options/AudioLanguageFragmentPresenter.kt index de64c710f08..89e19749e11 100644 --- a/app/src/main/java/org/oppia/android/app/options/AudioLanguageFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/options/AudioLanguageFragmentPresenter.kt @@ -43,6 +43,7 @@ class AudioLanguageFragmentPresenter @Inject constructor( private fun createRecyclerViewAdapter(): BindableAdapter { return singleTypeBuilderFactory.create() + .setLifecycleOwner(fragment) .registerViewDataBinderWithSameModelType( inflateDataBinding = LanguageItemsBinding::inflate, setViewModel = LanguageItemsBinding::setViewModel From e270424f43a5d5404409a74eb8cb46f060a2184a Mon Sep 17 00:00:00 2001 From: kevingitonga Date: Thu, 14 Jul 2022 17:57:36 +0300 Subject: [PATCH 12/34] Refactor StatePlayerRecyclerViewAssembler.kt to inject MultiTypeAdapterFactory --- .../app/player/state/StateFragmentPresenter.kt | 11 ++++++++--- .../state/StatePlayerRecyclerViewAssembler.kt | 18 ++++++++++++++---- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/org/oppia/android/app/player/state/StateFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/player/state/StateFragmentPresenter.kt index f0d22cecad7..d7145ec98ef 100755 --- a/app/src/main/java/org/oppia/android/app/player/state/StateFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/player/state/StateFragmentPresenter.kt @@ -31,6 +31,7 @@ import org.oppia.android.app.player.state.ConfettiConfig.MEDIUM_CONFETTI_BURST import org.oppia.android.app.player.state.ConfettiConfig.MINI_CONFETTI_BURST import org.oppia.android.app.player.state.listener.RouteToHintsAndSolutionListener import org.oppia.android.app.player.stopplaying.StopStatePlayingSessionWithSavedProgressListener +import org.oppia.android.app.recyclerview.BindableAdapter import org.oppia.android.app.topic.conceptcard.ConceptCardFragment.Companion.CONCEPT_CARD_DIALOG_FRAGMENT_TAG import org.oppia.android.app.utility.SplitScreenManager import org.oppia.android.app.viewmodel.ViewModelProvider @@ -68,7 +69,8 @@ class StateFragmentPresenter @Inject constructor( @DefaultResourceBucketName private val resourceBucketName: String, private val assemblerBuilderFactory: StatePlayerRecyclerViewAssembler.Builder.Factory, private val splitScreenManager: SplitScreenManager, - private val oppiaClock: OppiaClock + private val oppiaClock: OppiaClock, + private val multiTypeAdapterBuilderFactory: BindableAdapter.MultiTypeBuilder.Factory ) { private val routeToHintsAndSolutionListener = activity as RouteToHintsAndSolutionListener @@ -116,7 +118,8 @@ class StateFragmentPresenter @Inject constructor( assemblerBuilderFactory.create(resourceBucketName, entityType, profileId), binding.congratulationsTextView, binding.congratulationsTextConfettiView, - binding.fullScreenConfettiView + binding.fullScreenConfettiView, + multiTypeAdapterBuilderFactory ) val stateRecyclerViewAdapter = recyclerViewAssembler.adapter @@ -217,10 +220,12 @@ class StateFragmentPresenter @Inject constructor( builder: StatePlayerRecyclerViewAssembler.Builder, congratulationsTextView: TextView, congratulationsTextConfettiView: KonfettiView, - fullScreenConfettiView: KonfettiView + fullScreenConfettiView: KonfettiView, + multiTypeBuilderFactory: BindableAdapter.MultiTypeBuilder.Factory ): StatePlayerRecyclerViewAssembler { val isTablet = context.resources.getBoolean(R.bool.isTablet) return builder + .addAdapterBuilderFactory(multiTypeBuilderFactory) .hasConversationView(hasConversationView) .addContentSupport() .addFeedbackSupport() diff --git a/app/src/main/java/org/oppia/android/app/player/state/StatePlayerRecyclerViewAssembler.kt b/app/src/main/java/org/oppia/android/app/player/state/StatePlayerRecyclerViewAssembler.kt index cef7c72f9f9..c93c6689758 100644 --- a/app/src/main/java/org/oppia/android/app/player/state/StatePlayerRecyclerViewAssembler.kt +++ b/app/src/main/java/org/oppia/android/app/player/state/StatePlayerRecyclerViewAssembler.kt @@ -889,10 +889,9 @@ class StatePlayerRecyclerViewAssembler private constructor( private val resourceHandler: AppLanguageResourceHandler, private val translationController: TranslationController ) { - private val adapterBuilder = BindableAdapter.MultiTypeBuilder.Factory(fragment) - .create { - it.viewType - } + + private lateinit var adapterBuilder: + BindableAdapter.MultiTypeBuilder /** * Tracks features individually enabled for the assembler. No features are enabled by default. @@ -1184,6 +1183,17 @@ class StatePlayerRecyclerViewAssembler private constructor( } } + /** + * Add AdapterBuilderFactory passed through injection from [StateFragmentPresenter] + */ + fun addAdapterBuilderFactory + (multiTypeBuilderFactory: BindableAdapter.MultiTypeBuilder.Factory): Builder { + adapterBuilder = multiTypeBuilderFactory.create { + it.viewType + } + return this + } + /** * Adds support for automatically collapsing past wrong answers. This feature is not enabled * without [addPastAnswersSupport] also being enabled. From 459a9a58fc6cbe59ffad84e6581860c5d000db11 Mon Sep 17 00:00:00 2001 From: kevingitonga Date: Thu, 14 Jul 2022 20:04:42 +0300 Subject: [PATCH 13/34] Fix failing tests due to missed refactor of QuestionPlayerFragmentPresenter.kt to inject MultiTypeAdapter Factory. --- .../topic/questionplayer/QuestionPlayerFragmentPresenter.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/oppia/android/app/topic/questionplayer/QuestionPlayerFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/topic/questionplayer/QuestionPlayerFragmentPresenter.kt index 596ee8287d8..f16eec1ca16 100644 --- a/app/src/main/java/org/oppia/android/app/topic/questionplayer/QuestionPlayerFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/topic/questionplayer/QuestionPlayerFragmentPresenter.kt @@ -25,6 +25,7 @@ import org.oppia.android.app.player.state.StatePlayerRecyclerViewAssembler import org.oppia.android.app.player.state.listener.RouteToHintsAndSolutionListener import org.oppia.android.app.player.stopplaying.RestartPlayingSessionListener import org.oppia.android.app.player.stopplaying.StopStatePlayingSessionListener +import org.oppia.android.app.recyclerview.BindableAdapter import org.oppia.android.app.topic.conceptcard.ConceptCardFragment.Companion.CONCEPT_CARD_DIALOG_FRAGMENT_TAG import org.oppia.android.app.utility.SplitScreenManager import org.oppia.android.app.viewmodel.ViewModelProvider @@ -47,7 +48,8 @@ class QuestionPlayerFragmentPresenter @Inject constructor( private val oppiaLogger: OppiaLogger, @QuestionResourceBucketName private val resourceBucketName: String, private val assemblerBuilderFactory: StatePlayerRecyclerViewAssembler.Builder.Factory, - private val splitScreenManager: SplitScreenManager + private val splitScreenManager: SplitScreenManager, + private val multiTypeAdapterFactory: BindableAdapter.MultiTypeBuilder.Factory ) { // TODO(#503): Add tests for the question player. @@ -330,6 +332,7 @@ class QuestionPlayerFragmentPresenter @Inject constructor( // controller & possibly the ephemeral question data model. // TODO(#502): Add support for surfacing skills that need to be reviewed by the learner. return builder + .addAdapterBuilderFactory(multiTypeAdapterFactory) .hasConversationView(hasConversationView) .addContentSupport() .addFeedbackSupport() From 20c1ff3aa8c4c34d25048e2dbce847845b36538b Mon Sep 17 00:00:00 2001 From: kevingitonga Date: Mon, 18 Jul 2022 20:22:55 +0300 Subject: [PATCH 14/34] More updates as part of refactoring for issue #2658. --- .../AdministratorControlsFragmentPresenter.kt | 7 +- .../ProfileAndDeviceIdFragmentPresenter.kt | 7 +- .../MarkChaptersCompletedFragmentPresenter.kt | 5 +- .../promotedlist/ComingSoonTopicsListView.kt | 3 - .../promotedlist/PromotedStoryListView.kt | 31 ++++++-- .../options/AppLanguageFragmentPresenter.kt | 1 - .../options/AudioLanguageFragmentPresenter.kt | 1 - .../ReadingTextSizeFragmentPresenter.kt | 2 +- .../state/DragDropSortInteractionView.kt | 3 - .../player/state/SelectionInteractionView.kt | 6 +- .../player/state/StateFragmentPresenter.kt | 11 +-- .../state/StatePlayerRecyclerViewAssembler.kt | 33 ++++----- .../ProfileChooserFragmentPresenter.kt | 5 +- .../ProfileProgressFragmentPresenter.kt | 10 +-- .../app/recyclerview/BindableAdapter.kt | 70 ++++--------------- .../lessons/TopicLessonsFragmentPresenter.kt | 6 +- .../QuestionPlayerFragmentPresenter.kt | 5 +- 17 files changed, 77 insertions(+), 129 deletions(-) diff --git a/app/src/main/java/org/oppia/android/app/administratorcontrols/AdministratorControlsFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/administratorcontrols/AdministratorControlsFragmentPresenter.kt index da96366f5cf..6fc33e4c2b0 100644 --- a/app/src/main/java/org/oppia/android/app/administratorcontrols/AdministratorControlsFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/administratorcontrols/AdministratorControlsFragmentPresenter.kt @@ -31,7 +31,8 @@ import javax.inject.Inject @FragmentScope class AdministratorControlsFragmentPresenter @Inject constructor( private val activity: AppCompatActivity, - private val fragment: Fragment + private val fragment: Fragment, + private val multiTypeBuilder: BindableAdapter.MultiTypeBuilder.Factory ) { private lateinit var binding: AdministratorControlsFragmentBinding private lateinit var linearLayoutManager: LinearLayoutManager @@ -77,8 +78,8 @@ class AdministratorControlsFragmentPresenter @Inject constructor( /** Returns the recycler view adapter for the controls panel in administrator controls fragment. */ private fun createRecyclerViewAdapter(isMultipane: Boolean): BindableAdapter { - return BindableAdapter.MultiTypeBuilder - .Factory(fragment).create { viewModel -> + return multiTypeBuilder + .create { viewModel -> viewModel.isMultipane.set(isMultipane) when (viewModel) { is AdministratorControlsGeneralViewModel -> { diff --git a/app/src/main/java/org/oppia/android/app/administratorcontrols/learneranalytics/ProfileAndDeviceIdFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/administratorcontrols/learneranalytics/ProfileAndDeviceIdFragmentPresenter.kt index 542e712a40f..ca1b5cb1006 100644 --- a/app/src/main/java/org/oppia/android/app/administratorcontrols/learneranalytics/ProfileAndDeviceIdFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/administratorcontrols/learneranalytics/ProfileAndDeviceIdFragmentPresenter.kt @@ -17,7 +17,7 @@ import javax.inject.Inject class ProfileAndDeviceIdFragmentPresenter @Inject constructor( private val fragment: Fragment, private val profileListViewModelFactory: ProfileListViewModel.Factory, - private val multiTypeBuilderFactory: BindableAdapter.MultiTypeBuilder.Factory + private val adapterFactory: BindableAdapter.MultiTypeBuilder.Factory ) { private lateinit var binding: ProfileAndDeviceIdFragmentBinding @@ -40,15 +40,14 @@ class ProfileAndDeviceIdFragmentPresenter @Inject constructor( } private fun createRecyclerViewAdapter(): BindableAdapter { - return multiTypeBuilderFactory.create { viewModel -> + return adapterFactory.create { viewModel -> when (viewModel) { is DeviceIdItemViewModel -> ProfileListItemViewType.DEVICE_ID is ProfileLearnerIdItemViewModel -> ProfileListItemViewType.LEARNER_ID is SyncStatusItemViewModel -> ProfileListItemViewType.SYNC_STATUS else -> error("Encountered unexpected view model: $viewModel") } - }.setLifecycleOwner(fragment) + } .registerViewDataBinder( viewType = ProfileListItemViewType.DEVICE_ID, inflateDataBinding = ProfileListDeviceIdItemBinding::inflate, diff --git a/app/src/main/java/org/oppia/android/app/devoptions/markchapterscompleted/MarkChaptersCompletedFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/devoptions/markchapterscompleted/MarkChaptersCompletedFragmentPresenter.kt index fee0646f45f..63c448d7a88 100644 --- a/app/src/main/java/org/oppia/android/app/devoptions/markchapterscompleted/MarkChaptersCompletedFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/devoptions/markchapterscompleted/MarkChaptersCompletedFragmentPresenter.kt @@ -24,7 +24,7 @@ class MarkChaptersCompletedFragmentPresenter @Inject constructor( private val fragment: Fragment, private val viewModelProvider: ViewModelProvider, private val modifyLessonProgressController: ModifyLessonProgressController, - private val multiTypeBuilderFactory: BindableAdapter.MultiTypeBuilder.Factory + private val adapterFactory: BindableAdapter.MultiTypeBuilder.Factory ) : ChapterSelector { private lateinit var binding: MarkChaptersCompletedFragmentBinding private lateinit var linearLayoutManager: LinearLayoutManager @@ -95,8 +95,7 @@ class MarkChaptersCompletedFragmentPresenter @Inject constructor( } private fun createRecyclerViewAdapter(): BindableAdapter { - return multiTypeBuilderFactory.create { viewModel -> + return adapterFactory.create { viewModel -> when (viewModel) { is StorySummaryViewModel -> ViewType.VIEW_TYPE_STORY is ChapterSummaryViewModel -> ViewType.VIEW_TYPE_CHAPTER diff --git a/app/src/main/java/org/oppia/android/app/home/promotedlist/ComingSoonTopicsListView.kt b/app/src/main/java/org/oppia/android/app/home/promotedlist/ComingSoonTopicsListView.kt index e386b94ebf5..04239be8b94 100644 --- a/app/src/main/java/org/oppia/android/app/home/promotedlist/ComingSoonTopicsListView.kt +++ b/app/src/main/java/org/oppia/android/app/home/promotedlist/ComingSoonTopicsListView.kt @@ -32,9 +32,6 @@ class ComingSoonTopicsListView @JvmOverloads constructor( @Inject lateinit var oppiaLogger: OppiaLogger - @Inject - lateinit var fragment: Fragment - @Inject lateinit var singleTypeAdapterFactory: BindableAdapter.SingleTypeBuilder.Factory diff --git a/app/src/main/java/org/oppia/android/app/home/promotedlist/PromotedStoryListView.kt b/app/src/main/java/org/oppia/android/app/home/promotedlist/PromotedStoryListView.kt index e02141e63c5..05469ded4bb 100644 --- a/app/src/main/java/org/oppia/android/app/home/promotedlist/PromotedStoryListView.kt +++ b/app/src/main/java/org/oppia/android/app/home/promotedlist/PromotedStoryListView.kt @@ -38,10 +38,12 @@ class PromotedStoryListView @JvmOverloads constructor( @Inject lateinit var singleTypeBuilderFactory: BindableAdapter.SingleTypeBuilder.Factory + lateinit var promotedDataList: List + override fun onAttachedToWindow() { super.onAttachedToWindow() - - val viewComponentFactory = FragmentManager.findFragment(this) as ViewComponentFactory + val viewComponentFactory = + FragmentManager.findFragment(this) as ViewComponentFactory val viewComponent = viewComponentFactory.createViewComponent(this) as ViewComponentImpl viewComponent.inject(this) @@ -53,12 +55,31 @@ class PromotedStoryListView @JvmOverloads constructor( snapHelper.attachToRecyclerView(this) } + private fun checkIfComponentsInitialized() { + if (::bindingInterface.isInitialized && + ::bindingInterface.isInitialized && + ::oppiaLogger.isInitialized && + ::fragment.isInitialized && + ::singleTypeBuilderFactory.isInitialized && + ::promotedDataList.isInitialized + ) { + bindDataToAdapter() + } + } + /** * Sets the list of promoted stories that this view shows to the learner. * * @param newDataList the new list of stories to present */ fun setPromotedStoryList(newDataList: List?) { + if (newDataList != null) { + promotedDataList = newDataList + checkIfComponentsInitialized() + } + } + + private fun bindDataToAdapter() { // To reliably bind data only after the adapter is created, we manually set the data so we can first // check for the adapter; when using an existing [RecyclerViewBindingAdapter] there is no reliable // way to check that the adapter is created. @@ -67,10 +88,10 @@ class PromotedStoryListView @JvmOverloads constructor( if (adapter == null) { adapter = createAdapter() } - if (newDataList == null) { - oppiaLogger.w(PROMOTED_STORY_LIST_VIEW_TAG, "Failed to resolve new story list data") + if (::promotedDataList.isInitialized) { + (adapter as BindableAdapter<*>).setDataUnchecked(promotedDataList) } else { - (adapter as BindableAdapter<*>).setDataUnchecked(newDataList) + oppiaLogger.w(PROMOTED_STORY_LIST_VIEW_TAG, "Failed to resolve new story list data") } } diff --git a/app/src/main/java/org/oppia/android/app/options/AppLanguageFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/options/AppLanguageFragmentPresenter.kt index ebadf3921a0..1433ae55098 100644 --- a/app/src/main/java/org/oppia/android/app/options/AppLanguageFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/options/AppLanguageFragmentPresenter.kt @@ -43,7 +43,6 @@ class AppLanguageFragmentPresenter @Inject constructor( private fun createRecyclerViewAdapter(): BindableAdapter { return singleTypeBuilderFactory.create() - .setLifecycleOwner(fragment) .registerViewDataBinderWithSameModelType( inflateDataBinding = LanguageItemsBinding::inflate, setViewModel = LanguageItemsBinding::setViewModel diff --git a/app/src/main/java/org/oppia/android/app/options/AudioLanguageFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/options/AudioLanguageFragmentPresenter.kt index 89e19749e11..de64c710f08 100644 --- a/app/src/main/java/org/oppia/android/app/options/AudioLanguageFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/options/AudioLanguageFragmentPresenter.kt @@ -43,7 +43,6 @@ class AudioLanguageFragmentPresenter @Inject constructor( private fun createRecyclerViewAdapter(): BindableAdapter { return singleTypeBuilderFactory.create() - .setLifecycleOwner(fragment) .registerViewDataBinderWithSameModelType( inflateDataBinding = LanguageItemsBinding::inflate, setViewModel = LanguageItemsBinding::setViewModel diff --git a/app/src/main/java/org/oppia/android/app/options/ReadingTextSizeFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/options/ReadingTextSizeFragmentPresenter.kt index 9ea9259672a..6a1d4b145bf 100644 --- a/app/src/main/java/org/oppia/android/app/options/ReadingTextSizeFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/options/ReadingTextSizeFragmentPresenter.kt @@ -53,7 +53,7 @@ class ReadingTextSizeFragmentPresenter @Inject constructor( .registerViewDataBinderWithSameModelType( inflateDataBinding = TextSizeItemsBinding::inflate, setViewModel = TextSizeItemsBinding::setViewModel - ).setLifecycleOwner(fragment) + ) .build() } diff --git a/app/src/main/java/org/oppia/android/app/player/state/DragDropSortInteractionView.kt b/app/src/main/java/org/oppia/android/app/player/state/DragDropSortInteractionView.kt index 7632ada52ae..289bcc69301 100644 --- a/app/src/main/java/org/oppia/android/app/player/state/DragDropSortInteractionView.kt +++ b/app/src/main/java/org/oppia/android/app/player/state/DragDropSortInteractionView.kt @@ -53,9 +53,6 @@ class DragDropSortInteractionView @JvmOverloads constructor( @Inject lateinit var viewBindingShim: ViewBindingShim - @Inject - lateinit var fragment: Fragment - @Inject lateinit var singleTypeBuilderFactory: BindableAdapter.SingleTypeBuilder.Factory diff --git a/app/src/main/java/org/oppia/android/app/player/state/SelectionInteractionView.kt b/app/src/main/java/org/oppia/android/app/player/state/SelectionInteractionView.kt index d1785f22bdc..b7dcf363372 100644 --- a/app/src/main/java/org/oppia/android/app/player/state/SelectionInteractionView.kt +++ b/app/src/main/java/org/oppia/android/app/player/state/SelectionInteractionView.kt @@ -45,9 +45,6 @@ class SelectionInteractionView @JvmOverloads constructor( @Inject lateinit var bindingInterface: ViewBindingShim - @Inject - lateinit var fragment: Fragment - @Inject lateinit var singleTypeBuilderFactory: BindableAdapter.SingleTypeBuilder.Factory @@ -112,8 +109,7 @@ class SelectionInteractionView @JvmOverloads constructor( ) .build() SelectionItemInputType.RADIO_BUTTONS -> - BindableAdapter.SingleTypeBuilder - .Factory(fragment).create() + singleTypeBuilderFactory.create() .registerViewBinder( inflateView = { parent -> bindingInterface.provideMultipleChoiceInteractionItemsInflatedView( diff --git a/app/src/main/java/org/oppia/android/app/player/state/StateFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/player/state/StateFragmentPresenter.kt index d7145ec98ef..f0d22cecad7 100755 --- a/app/src/main/java/org/oppia/android/app/player/state/StateFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/player/state/StateFragmentPresenter.kt @@ -31,7 +31,6 @@ import org.oppia.android.app.player.state.ConfettiConfig.MEDIUM_CONFETTI_BURST import org.oppia.android.app.player.state.ConfettiConfig.MINI_CONFETTI_BURST import org.oppia.android.app.player.state.listener.RouteToHintsAndSolutionListener import org.oppia.android.app.player.stopplaying.StopStatePlayingSessionWithSavedProgressListener -import org.oppia.android.app.recyclerview.BindableAdapter import org.oppia.android.app.topic.conceptcard.ConceptCardFragment.Companion.CONCEPT_CARD_DIALOG_FRAGMENT_TAG import org.oppia.android.app.utility.SplitScreenManager import org.oppia.android.app.viewmodel.ViewModelProvider @@ -69,8 +68,7 @@ class StateFragmentPresenter @Inject constructor( @DefaultResourceBucketName private val resourceBucketName: String, private val assemblerBuilderFactory: StatePlayerRecyclerViewAssembler.Builder.Factory, private val splitScreenManager: SplitScreenManager, - private val oppiaClock: OppiaClock, - private val multiTypeAdapterBuilderFactory: BindableAdapter.MultiTypeBuilder.Factory + private val oppiaClock: OppiaClock ) { private val routeToHintsAndSolutionListener = activity as RouteToHintsAndSolutionListener @@ -118,8 +116,7 @@ class StateFragmentPresenter @Inject constructor( assemblerBuilderFactory.create(resourceBucketName, entityType, profileId), binding.congratulationsTextView, binding.congratulationsTextConfettiView, - binding.fullScreenConfettiView, - multiTypeAdapterBuilderFactory + binding.fullScreenConfettiView ) val stateRecyclerViewAdapter = recyclerViewAssembler.adapter @@ -220,12 +217,10 @@ class StateFragmentPresenter @Inject constructor( builder: StatePlayerRecyclerViewAssembler.Builder, congratulationsTextView: TextView, congratulationsTextConfettiView: KonfettiView, - fullScreenConfettiView: KonfettiView, - multiTypeBuilderFactory: BindableAdapter.MultiTypeBuilder.Factory + fullScreenConfettiView: KonfettiView ): StatePlayerRecyclerViewAssembler { val isTablet = context.resources.getBoolean(R.bool.isTablet) return builder - .addAdapterBuilderFactory(multiTypeBuilderFactory) .hasConversationView(hasConversationView) .addContentSupport() .addFeedbackSupport() diff --git a/app/src/main/java/org/oppia/android/app/player/state/StatePlayerRecyclerViewAssembler.kt b/app/src/main/java/org/oppia/android/app/player/state/StatePlayerRecyclerViewAssembler.kt index c93c6689758..b30a6843920 100644 --- a/app/src/main/java/org/oppia/android/app/player/state/StatePlayerRecyclerViewAssembler.kt +++ b/app/src/main/java/org/oppia/android/app/player/state/StatePlayerRecyclerViewAssembler.kt @@ -887,11 +887,13 @@ class StatePlayerRecyclerViewAssembler private constructor( private val interactionViewModelFactoryMap: Map, private val backgroundCoroutineDispatcher: CoroutineDispatcher, private val resourceHandler: AppLanguageResourceHandler, - private val translationController: TranslationController + private val translationController: TranslationController, + private val adapterBuilderFactory: BindableAdapter.MultiTypeBuilder.Factory, + private val singleAdapterFactory: BindableAdapter.SingleTypeBuilder.Factory ) { - private lateinit var adapterBuilder: - BindableAdapter.MultiTypeBuilder + private var adapterBuilder: BindableAdapter.MultiTypeBuilder = adapterBuilderFactory.create { it.viewType } /** * Tracks features individually enabled for the assembler. No features are enabled by default. @@ -1116,8 +1118,7 @@ class StatePlayerRecyclerViewAssembler private constructor( gcsEntityId: String, supportsConceptCards: Boolean ): BindableAdapter { - return BindableAdapter.SingleTypeBuilder - .Factory(fragment).create() + return singleAdapterFactory.create() .registerViewBinder( inflateView = { parent -> SubmittedAnswerListItemBinding.inflate( @@ -1138,8 +1139,7 @@ class StatePlayerRecyclerViewAssembler private constructor( gcsEntityId: String, supportsConceptCards: Boolean ): BindableAdapter { - return BindableAdapter.SingleTypeBuilder - .Factory(fragment).create() + return singleAdapterFactory.create() .registerViewBinder( inflateView = { parent -> SubmittedHtmlAnswerItemBinding.inflate( @@ -1183,17 +1183,6 @@ class StatePlayerRecyclerViewAssembler private constructor( } } - /** - * Add AdapterBuilderFactory passed through injection from [StateFragmentPresenter] - */ - fun addAdapterBuilderFactory - (multiTypeBuilderFactory: BindableAdapter.MultiTypeBuilder.Factory): Builder { - adapterBuilder = multiTypeBuilderFactory.create { - it.viewType - } - return this - } - /** * Adds support for automatically collapsing past wrong answers. This feature is not enabled * without [addPastAnswersSupport] also being enabled. @@ -1400,7 +1389,9 @@ class StatePlayerRecyclerViewAssembler private constructor( String, @JvmSuppressWildcards InteractionItemFactory>, @BackgroundDispatcher private val backgroundCoroutineDispatcher: CoroutineDispatcher, private val resourceHandler: AppLanguageResourceHandler, - private val translationController: TranslationController + private val translationController: TranslationController, + private val multiAdapterBuilderFactory: BindableAdapter.MultiTypeBuilder.Factory, + private val singleAdapterFactory: BindableAdapter.SingleTypeBuilder.Factory ) { /** * Returns a new [Builder] for the specified GCS resource bucket information for loading @@ -1418,7 +1409,9 @@ class StatePlayerRecyclerViewAssembler private constructor( interactionViewModelFactoryMap, backgroundCoroutineDispatcher, resourceHandler, - translationController + translationController, + multiAdapterBuilderFactory, + singleAdapterFactory ) } } diff --git a/app/src/main/java/org/oppia/android/app/profile/ProfileChooserFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/profile/ProfileChooserFragmentPresenter.kt index 5e71216c7a1..b3a86046b56 100644 --- a/app/src/main/java/org/oppia/android/app/profile/ProfileChooserFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/profile/ProfileChooserFragmentPresenter.kt @@ -66,7 +66,7 @@ class ProfileChooserFragmentPresenter @Inject constructor( private val viewModelProvider: ViewModelProvider, private val profileManagementController: ProfileManagementController, private val oppiaLogger: OppiaLogger, - private val multiTypeAdapterBuilder: BindableAdapter.MultiTypeBuilder.Factory + private val adapterBuilder: BindableAdapter.MultiTypeBuilder.Factory ) { private lateinit var binding: ProfileChooserFragmentBinding val hasProfileEverBeenAddedValue = ObservableField(true) @@ -149,8 +149,7 @@ class ProfileChooserFragmentPresenter @Inject constructor( } private fun createRecyclerViewAdapter(): BindableAdapter { - return multiTypeAdapterBuilder.create( + return adapterBuilder.create( ProfileChooserUiModel::getModelTypeCase ) .registerViewDataBinderWithSameModelType( diff --git a/app/src/main/java/org/oppia/android/app/profileprogress/ProfileProgressFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/profileprogress/ProfileProgressFragmentPresenter.kt index c449ee04532..e39fdbed8a7 100644 --- a/app/src/main/java/org/oppia/android/app/profileprogress/ProfileProgressFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/profileprogress/ProfileProgressFragmentPresenter.kt @@ -20,15 +20,11 @@ private const val TAG_PROFILE_PICTURE_EDIT_DIALOG = "PROFILE_PICTURE_EDIT_DIALOG @FragmentScope class ProfileProgressFragmentPresenter @Inject constructor( private val activity: AppCompatActivity, - private val fragment: Fragment + private val fragment: Fragment, + private val viewModel: ProfileProgressViewModel, + private val multiTypeAdapterBuilder: BindableAdapter.MultiTypeBuilder.Factory ) { - @Inject - lateinit var viewModel: ProfileProgressViewModel - - @Inject - lateinit var multiTypeAdapterBuilder: BindableAdapter.MultiTypeBuilder.Factory - fun handleCreateView( inflater: LayoutInflater, container: ViewGroup?, diff --git a/app/src/main/java/org/oppia/android/app/recyclerview/BindableAdapter.kt b/app/src/main/java/org/oppia/android/app/recyclerview/BindableAdapter.kt index 5fa53d2d6f8..cb013f8d859 100644 --- a/app/src/main/java/org/oppia/android/app/recyclerview/BindableAdapter.kt +++ b/app/src/main/java/org/oppia/android/app/recyclerview/BindableAdapter.kt @@ -5,9 +5,7 @@ import android.view.View import android.view.ViewGroup import androidx.databinding.ViewDataBinding import androidx.fragment.app.Fragment -import androidx.lifecycle.LifecycleOwner import androidx.recyclerview.widget.RecyclerView -import java.lang.ref.WeakReference import javax.inject.Inject import kotlin.reflect.KClass @@ -95,55 +93,6 @@ class BindableAdapter internal constructor( internal abstract fun bind(data: T) } - /** - * The base builder for [BindableAdapter]. This class should not be used directly--use either - * [SingleTypeBuilder] or [MultiTypeBuilder] instead. - */ - abstract class BaseBuilder { - /** - * A [WeakReference] to a [LifecycleOwner] for databinding inflation. See [setLifecycleOwner]. - * Note that this needs to be a weak reference so that long-held references to the adapter do - * not potentially leak lifecycle owners (such as fragments and activities). - */ - private var lifecycleOwnerRef: WeakReference? = null - - /** - * Sets the [LifecycleOwner] corresponding to this adapter. This will automatically be used as - * the lifecycle owner for all databinding classes created during view inflation. Note that the - * adapter holds a weak reference to the owner to ensure long-lived references to the adapter - * class itself does not result in leaks, however it's up to the caller's responsibility to make - * sure that the adapter itself is not actually used after the lifecycle owner has expired - * (otherwise the app may crash). - * - * @return this - */ - fun setLifecycleOwner(lifecycleOwner: LifecycleOwner): BuilderType { - check(lifecycleOwnerRef == null) { - "A lifecycle owner has already been bound to this adapter." - } - lifecycleOwnerRef = WeakReference(lifecycleOwner) - - // This cast is not, strictly speaking, safe, but child classes are expected to inherit from - // the builder & pass their own type in. - @Suppress("UNCHECKED_CAST") return this as BuilderType - } - - /** - * Returns the [LifecycleOwner] bound to this adapter, or null if there isn't one. This method - * will throw if there was a lifecycle owner bound but is now expired. - */ - protected fun getLifecycleOwner(): LifecycleOwner? { - // Crash if the lifecycle owner has been cleaned up since it's not valid to use the adapter - // with an old lifecycle owner, and silently ignoring this may result in part of the layout - // not responding to events. - return lifecycleOwnerRef?.let { ref -> - checkNotNull(ref.get()) { - "Attempted to inflate data binding with expired lifecycle owner" - } - } - } - } - /** * Constructs a new [BindableAdapter] that for a single view type. * @@ -152,7 +101,7 @@ class BindableAdapter internal constructor( class SingleTypeBuilder( private val dataClassType: KClass, private val fragment: Fragment - ) : BaseBuilder>() { + ) { private lateinit var viewHolderFactory: ViewHolderFactory /** @@ -223,7 +172,8 @@ class BindableAdapter internal constructor( viewGroup, /* attachToRoot= */ false ) - binding.lifecycleOwner = getLifecycleOwner() + + binding.lifecycleOwner = fragment.viewLifecycleOwner object : BindableViewHolder(binding.root) { override fun bind(data: T) { setViewModel(binding, data) @@ -243,9 +193,13 @@ class BindableAdapter internal constructor( ) } + /** Fragment injectable factory to create new [SingleTypeBuilder] */ class Factory @Inject constructor( val fragment: Fragment ) { + /** + * Returns a new [SingleTypeBuilder] for the specified Data class type. + */ inline fun create(): SingleTypeBuilder { return SingleTypeBuilder(T::class, fragment) } @@ -262,7 +216,7 @@ class BindableAdapter internal constructor( private val dataClassType: KClass, private val computeViewType: ComputeViewType, private val fragment: Fragment - ) : BaseBuilder>() { + ) { private var viewHolderFactoryMap: MutableMap> = HashMap() /** @@ -346,7 +300,8 @@ class BindableAdapter internal constructor( viewGroup, /* attachToRoot= */ false ) - binding.lifecycleOwner = getLifecycleOwner() + + binding.lifecycleOwner = fragment.viewLifecycleOwner object : BindableViewHolder(binding.root) { override fun bind(data: T) { setViewModel(binding, transformViewModel(data)) @@ -374,9 +329,14 @@ class BindableAdapter internal constructor( ) } + /** Fragment injectable factory to create new [MultiTypeBuilder] */ class Factory @Inject constructor( val fragment: Fragment ) { + + /** + * Returns a new [MultiTypeBuilder] for the specified data class type. + */ inline fun > create( noinline computeViewType: ComputeViewType ): MultiTypeBuilder { diff --git a/app/src/main/java/org/oppia/android/app/topic/lessons/TopicLessonsFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/topic/lessons/TopicLessonsFragmentPresenter.kt index ca5ec11b19f..e148a5aba53 100644 --- a/app/src/main/java/org/oppia/android/app/topic/lessons/TopicLessonsFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/topic/lessons/TopicLessonsFragmentPresenter.kt @@ -35,7 +35,8 @@ class TopicLessonsFragmentPresenter @Inject constructor( private val oppiaLogger: OppiaLogger, private val explorationDataController: ExplorationDataController, private val explorationCheckpointController: ExplorationCheckpointController, - private val multiTypeAdapterFactory: BindableAdapter.MultiTypeBuilder.Factory + private val multiTypeAdapterFactory: BindableAdapter.MultiTypeBuilder.Factory, + private val singleTypeBuilderFactory: BindableAdapter.SingleTypeBuilder.Factory ) { private val routeToResumeLessonListener = activity as RouteToResumeLessonListener @@ -207,8 +208,7 @@ class TopicLessonsFragmentPresenter @Inject constructor( } private fun createChapterRecyclerViewAdapter(): BindableAdapter { - return BindableAdapter.SingleTypeBuilder - .Factory(fragment).create() + return singleTypeBuilderFactory.create() .registerViewDataBinderWithSameModelType( inflateDataBinding = LessonsChapterViewBinding::inflate, setViewModel = LessonsChapterViewBinding::setViewModel diff --git a/app/src/main/java/org/oppia/android/app/topic/questionplayer/QuestionPlayerFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/topic/questionplayer/QuestionPlayerFragmentPresenter.kt index f16eec1ca16..596ee8287d8 100644 --- a/app/src/main/java/org/oppia/android/app/topic/questionplayer/QuestionPlayerFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/topic/questionplayer/QuestionPlayerFragmentPresenter.kt @@ -25,7 +25,6 @@ import org.oppia.android.app.player.state.StatePlayerRecyclerViewAssembler import org.oppia.android.app.player.state.listener.RouteToHintsAndSolutionListener import org.oppia.android.app.player.stopplaying.RestartPlayingSessionListener import org.oppia.android.app.player.stopplaying.StopStatePlayingSessionListener -import org.oppia.android.app.recyclerview.BindableAdapter import org.oppia.android.app.topic.conceptcard.ConceptCardFragment.Companion.CONCEPT_CARD_DIALOG_FRAGMENT_TAG import org.oppia.android.app.utility.SplitScreenManager import org.oppia.android.app.viewmodel.ViewModelProvider @@ -48,8 +47,7 @@ class QuestionPlayerFragmentPresenter @Inject constructor( private val oppiaLogger: OppiaLogger, @QuestionResourceBucketName private val resourceBucketName: String, private val assemblerBuilderFactory: StatePlayerRecyclerViewAssembler.Builder.Factory, - private val splitScreenManager: SplitScreenManager, - private val multiTypeAdapterFactory: BindableAdapter.MultiTypeBuilder.Factory + private val splitScreenManager: SplitScreenManager ) { // TODO(#503): Add tests for the question player. @@ -332,7 +330,6 @@ class QuestionPlayerFragmentPresenter @Inject constructor( // controller & possibly the ephemeral question data model. // TODO(#502): Add support for surfacing skills that need to be reviewed by the learner. return builder - .addAdapterBuilderFactory(multiTypeAdapterFactory) .hasConversationView(hasConversationView) .addContentSupport() .addFeedbackSupport() From dfed2cb9c763a50e156936761df0f4bc8001d229 Mon Sep 17 00:00:00 2001 From: kevingitonga Date: Tue, 19 Jul 2022 16:26:51 +0300 Subject: [PATCH 15/34] Refactor PromotedStoryListView.kt BindableAdapterTest.kt classes to provide Adapter through injection for isuue #2658. --- .../promotedlist/PromotedStoryListView.kt | 34 +++-- .../app/recyclerview/BindableAdapter.kt | 38 ++++- .../BindableAdapterTestFragmentPresenter.kt | 9 +- .../app/recyclerview/BindableAdapterTest.kt | 144 +++++++++--------- 4 files changed, 133 insertions(+), 92 deletions(-) diff --git a/app/src/main/java/org/oppia/android/app/home/promotedlist/PromotedStoryListView.kt b/app/src/main/java/org/oppia/android/app/home/promotedlist/PromotedStoryListView.kt index 05469ded4bb..b01e0c6c7c7 100644 --- a/app/src/main/java/org/oppia/android/app/home/promotedlist/PromotedStoryListView.kt +++ b/app/src/main/java/org/oppia/android/app/home/promotedlist/PromotedStoryListView.kt @@ -41,18 +41,30 @@ class PromotedStoryListView @JvmOverloads constructor( lateinit var promotedDataList: List override fun onAttachedToWindow() { - super.onAttachedToWindow() - val viewComponentFactory = - FragmentManager.findFragment(this) as ViewComponentFactory - val viewComponent = viewComponentFactory.createViewComponent(this) as ViewComponentImpl - viewComponent.inject(this) + try { + super.onAttachedToWindow() + val viewComponentFactory = + FragmentManager.findFragment(this) as ViewComponentFactory + val viewComponent = viewComponentFactory.createViewComponent(this) as ViewComponentImpl + viewComponent.inject(this) - // The StartSnapHelper is used to snap between items rather than smooth scrolling, so that - // the item is completely visible in [HomeFragment] as soon as learner lifts the finger - // after scrolling. - val snapHelper = StartSnapHelper() - onFlingListener = null - snapHelper.attachToRecyclerView(this) + // The StartSnapHelper is used to snap between items rather than smooth scrolling, so that + // the item is completely visible in [HomeFragment] as soon as learner lifts the finger + // after scrolling. + val snapHelper = StartSnapHelper() + onFlingListener = null + snapHelper.attachToRecyclerView(this) + + checkIfComponentsInitialized() + } catch (e: IllegalStateException) { + if (::oppiaLogger.isInitialized) { + oppiaLogger.e( + "PromotedStoryListView", + "Throws exception on attach to window", + e + ) + } + } } private fun checkIfComponentsInitialized() { diff --git a/app/src/main/java/org/oppia/android/app/recyclerview/BindableAdapter.kt b/app/src/main/java/org/oppia/android/app/recyclerview/BindableAdapter.kt index cb013f8d859..fa1a036e9ee 100644 --- a/app/src/main/java/org/oppia/android/app/recyclerview/BindableAdapter.kt +++ b/app/src/main/java/org/oppia/android/app/recyclerview/BindableAdapter.kt @@ -5,7 +5,9 @@ import android.view.View import android.view.ViewGroup import androidx.databinding.ViewDataBinding import androidx.fragment.app.Fragment +import androidx.lifecycle.LifecycleOwner import androidx.recyclerview.widget.RecyclerView +import java.lang.ref.WeakReference import javax.inject.Inject import kotlin.reflect.KClass @@ -93,6 +95,34 @@ class BindableAdapter internal constructor( internal abstract fun bind(data: T) } + /** + * The base builder for [BindableAdapter]. This class should not be used directly--use either + * [SingleTypeBuilder] or [MultiTypeBuilder] instead. + */ + abstract class BaseBuilder(fragment: Fragment) { + /** + * A [WeakReference] to a [LifecycleOwner] for databinding inflation. + * Note that this needs to be a weak reference so that long-held references to the adapter do + * not potentially leak lifecycle owners (such as fragments and activities). + */ + private var lifecycleOwnerRef: WeakReference = WeakReference(fragment) + + /** + * Returns the [LifecycleOwner] bound to this adapter, or null if there isn't one. This method + * will throw if there was a lifecycle owner bound but is now expired. + */ + protected fun getLifecycleOwner(): LifecycleOwner? { + // Crash if the lifecycle owner has been cleaned up since it's not valid to use the adapter + // with an old lifecycle owner, and silently ignoring this may result in part of the layout + // not responding to events. + return lifecycleOwnerRef?.let { ref -> + checkNotNull(ref.get()) { + "Attempted to inflate data binding with expired lifecycle owner" + } + } + } + } + /** * Constructs a new [BindableAdapter] that for a single view type. * @@ -101,7 +131,7 @@ class BindableAdapter internal constructor( class SingleTypeBuilder( private val dataClassType: KClass, private val fragment: Fragment - ) { + ) : BaseBuilder(fragment) { private lateinit var viewHolderFactory: ViewHolderFactory /** @@ -173,10 +203,10 @@ class BindableAdapter internal constructor( /* attachToRoot= */ false ) - binding.lifecycleOwner = fragment.viewLifecycleOwner object : BindableViewHolder(binding.root) { override fun bind(data: T) { setViewModel(binding, data) + binding.lifecycleOwner = getLifecycleOwner() } } } @@ -216,7 +246,7 @@ class BindableAdapter internal constructor( private val dataClassType: KClass, private val computeViewType: ComputeViewType, private val fragment: Fragment - ) { + ) : BaseBuilder(fragment) { private var viewHolderFactoryMap: MutableMap> = HashMap() /** @@ -301,10 +331,10 @@ class BindableAdapter internal constructor( /* attachToRoot= */ false ) - binding.lifecycleOwner = fragment.viewLifecycleOwner object : BindableViewHolder(binding.root) { override fun bind(data: T) { setViewModel(binding, transformViewModel(data)) + binding.lifecycleOwner = getLifecycleOwner() } } } diff --git a/app/src/main/java/org/oppia/android/app/testing/BindableAdapterTestFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/testing/BindableAdapterTestFragmentPresenter.kt index a6b255058db..25961d8ba67 100644 --- a/app/src/main/java/org/oppia/android/app/testing/BindableAdapterTestFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/testing/BindableAdapterTestFragmentPresenter.kt @@ -12,6 +12,8 @@ import javax.inject.Inject /** The test-only fragment presenter corresponding to [BindableAdapterTestFragment]. */ class BindableAdapterTestFragmentPresenter @Inject constructor( private val fragment: Fragment, + private val singleTypeBuilder: BindableAdapter.SingleTypeBuilder.Factory, + private val multiTypeBuilder: BindableAdapter.MultiTypeBuilder.Factory, private val testBindableAdapterFactory: BindableAdapterFactory, @VisibleForTesting val viewModel: BindableAdapterTestViewModel ) { @@ -22,7 +24,7 @@ class BindableAdapterTestFragmentPresenter @Inject constructor( /* attachToRoot= */ false ) binding.testRecyclerView.apply { - adapter = testBindableAdapterFactory.create(fragment) + adapter = testBindableAdapterFactory.create(singleTypeBuilder, multiTypeBuilder) } binding.let { it.viewModel = viewModel @@ -33,6 +35,9 @@ class BindableAdapterTestFragmentPresenter @Inject constructor( /** Factory for creating new [BindableAdapter]s for the current fragment. */ interface BindableAdapterFactory { - fun create(fragment: Fragment): BindableAdapter + fun create( + singleTypeBuilder: BindableAdapter.SingleTypeBuilder.Factory, + multiTypeBuilder: BindableAdapter.MultiTypeBuilder.Factory + ): BindableAdapter } } diff --git a/app/src/sharedTest/java/org/oppia/android/app/recyclerview/BindableAdapterTest.kt b/app/src/sharedTest/java/org/oppia/android/app/recyclerview/BindableAdapterTest.kt index ef436215158..7c0ca780cf0 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/recyclerview/BindableAdapterTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/recyclerview/BindableAdapterTest.kt @@ -6,7 +6,6 @@ import android.view.LayoutInflater import android.view.ViewGroup import android.widget.TextView import androidx.appcompat.app.AppCompatActivity -import androidx.fragment.app.Fragment import androidx.lifecycle.MutableLiveData import androidx.recyclerview.widget.RecyclerView import androidx.test.core.app.ActivityScenario.launch @@ -163,10 +162,9 @@ class BindableAdapterTest { @Test fun testSingleTypeAdapter_withOneViewType_noData_bindsNoViews() { - val testFragment = Fragment() // Set up the adapter to be used for this test. TestModule.testAdapterFactory = - { createSingleViewTypeNoDataBindingBindableAdapter(testFragment) } + { singleFactory, _ -> createSingleViewTypeNoDataBindingBindableAdapter(singleFactory) } launch(BindableAdapterTestActivity::class.java).use { scenario -> scenario.onActivity { activity -> @@ -180,10 +178,10 @@ class BindableAdapterTest { @Test fun testSingleTypeAdapter_withOneViewType_setItem_automaticallyBindsView() { - val testFragment = Fragment() // Set up the adapter to be used for this test. - TestModule.testAdapterFactory = - { createSingleViewTypeNoDataBindingBindableAdapter(testFragment) } + TestModule.testAdapterFactory = { singleTypeFactory, _ -> + createSingleViewTypeNoDataBindingBindableAdapter(singleTypeFactory) + } launch(BindableAdapterTestActivity::class.java).use { scenario -> scenario.onActivity { activity -> @@ -205,10 +203,10 @@ class BindableAdapterTest { @Test fun testSingleTypeAdapter_withOneViewType_nullData_bindsNoViews() { - val testFragment = Fragment() // Set up the adapter to be used for this test. - TestModule.testAdapterFactory = - { createSingleViewTypeNoDataBindingBindableAdapter(testFragment) } + TestModule.testAdapterFactory = { singleTypeFactory, _ -> + createSingleViewTypeNoDataBindingBindableAdapter(singleTypeFactory) + } launch(BindableAdapterTestActivity::class.java).use { scenario -> scenario.onActivity { activity -> @@ -221,10 +219,10 @@ class BindableAdapterTest { @Test fun testSingleTypeAdapter_withOneViewType_setMultipleItems_automaticallyBinds() { - val testFragment = Fragment() // Set up the adapter to be used for this test. - TestModule.testAdapterFactory = - { createSingleViewTypeNoDataBindingBindableAdapter(testFragment) } + TestModule.testAdapterFactory = { singleTypeFactory, _ -> + createSingleViewTypeNoDataBindingBindableAdapter(singleTypeFactory) + } launch(BindableAdapterTestActivity::class.java).use { scenario -> scenario.onActivity { activity -> @@ -249,10 +247,9 @@ class BindableAdapterTest { @Test fun testMultiTypeAdapter_withTwoViewTypes_setItems_autoBindsCorrectItemsPerTypes() { - val testFragment = Fragment() // Set up the adapter to be used for this test. TestModule.testAdapterFactory = - { createMultiViewTypeNoDataBindingBindableAdapter(testFragment) } + { _, multiTypeFactory -> createMultiViewTypeNoDataBindingBindableAdapter(multiTypeFactory) } launch(BindableAdapterTestActivity::class.java).use { scenario -> scenario.onActivity { activity -> @@ -287,10 +284,10 @@ class BindableAdapterTest { @Test fun testSingleTypeAdapter_withOneDataBoundViewType_setItem_automaticallyBindsView() { - val testFragment = Fragment() // Set up the adapter to be used for this test. - TestModule.testAdapterFactory = - { createSingleViewTypeWithDataBindingBindableAdapter(testFragment) } + TestModule.testAdapterFactory = { singleTypeFactory, _ -> + createSingleViewTypeWithDataBindingBindableAdapter(singleTypeFactory) + } launch(BindableAdapterTestActivity::class.java).use { scenario -> scenario.onActivity { activity -> @@ -312,13 +309,11 @@ class BindableAdapterTest { @Test fun testSingleTypeAdapter_withTwoDataBoundViewTypes_setItems_autoBindsCorrectItemsPerTypes() { - val testFragment = Fragment() // Set up the adapter to be used for this test. - TestModule.testAdapterFactory = { - createSingleViewTypeWithDataBindingBindableAdapter( - testFragment - ) - } + TestModule.testAdapterFactory = + { singleTypeFactory, _ -> + createSingleViewTypeWithDataBindingBindableAdapter(singleTypeFactory) + } launch(BindableAdapterTestActivity::class.java).use { scenario -> scenario.onActivity { activity -> @@ -344,10 +339,9 @@ class BindableAdapterTest { @Test fun testMultiTypeAdapter_partiallyDataBoundViewTypes_setItems_autoBindsCorrectItemsPerTypes() { - val testFragment = Fragment() // Set up the adapter to be used for this test. TestModule.testAdapterFactory = - { createMultiViewTypeWithDataBindingBindableAdapter(testFragment) } + { _, multiTypeFactory -> createMultiViewTypeWithDataBindingBindableAdapter(multiTypeFactory) } launch(BindableAdapterTestActivity::class.java).use { scenario -> scenario.onActivity { activity -> @@ -382,38 +376,31 @@ class BindableAdapterTest { @Test fun testSingleTypeAdapter_setLifecycleOwnerTwice_throwsException() { - val testFragment = Fragment() - val exception = assertThrows(IllegalStateException::class) { - SingleTypeBuilder - .Factory(testFragment).create() - .setLifecycleOwner(testFragment) - .setLifecycleOwner(testFragment) - .build() + TestModule.testAdapterFactory = + { singleTypeAdapter, _ -> createSingleAdapterWithoutView(singleTypeAdapter) } } + assertThat(exception).hasMessageThat().contains("lifecycle owner has already been bound") } @Test fun testMultiTypeAdapter_setLifecycleOwnerTwice_throwsException() { - val testFragment = Fragment() val exception = assertThrows(IllegalStateException::class) { - MultiTypeBuilder - .Factory(testFragment).create(ViewModelType.Companion::deriveTypeFrom) - .setLifecycleOwner(testFragment) - .setLifecycleOwner(testFragment) - .build() + TestModule.testAdapterFactory = + { _, multiTypeFactory -> createMultiViewTypeNoDataBindingBindableAdapter(multiTypeFactory) } } + assertThat(exception).hasMessageThat().contains("lifecycle owner has already been bound") } @Test fun testSingleTypeAdapter_withLiveData_noLifecycleOwner_doesNotRebindLiveDataValues() { - val testFragment = Fragment() // Set up the adapter to be used for this test. - TestModule.testAdapterFactory = - { createSingleViewTypeWithDataBindingAndLiveDataAdapter(testFragment) } + TestModule.testAdapterFactory = { singleTypeFactory, _ -> + createSingleViewTypeWithDataBindingAndLiveDataAdapter(singleTypeFactory) + } launch(BindableAdapterTestActivity::class.java).use { scenario -> val itemLiveData = MutableLiveData("initial") @@ -438,8 +425,8 @@ class BindableAdapterTest { @Test fun testSingleTypeAdapter_withLiveData_withLifecycleOwner_rebindsLiveDataValues() { // Set up the adapter to be used for this test. - TestModule.testAdapterFactory = { fragment -> - createSingleViewTypeWithDataBindingAndLiveDataAdapter(lifecycleOwner = fragment) + TestModule.testAdapterFactory = { singleTypeFactory, _ -> + createSingleViewTypeWithDataBindingAndLiveDataAdapter(singleTypeFactory) } launch(BindableAdapterTestActivity::class.java).use { scenario -> @@ -465,10 +452,9 @@ class BindableAdapterTest { @Test fun testMultiTypeAdapter_withLiveData_noLifecycleOwner_doesNotRebindLiveDataValues() { - val testFragment = Fragment() // Set up the adapter to be used for this test. TestModule.testAdapterFactory = - { createMultiViewTypeWithDataBindingBindableAdapter(testFragment) } + { _, multiTypeFactory -> createMultiViewTypeWithDataBindingBindableAdapter(multiTypeFactory) } launch(BindableAdapterTestActivity::class.java).use { scenario -> val itemLiveData = MutableLiveData("initial") @@ -493,9 +479,8 @@ class BindableAdapterTest { @Test fun testMultiTypeAdapter_withLiveData_withLifecycleOwner_rebindsLiveDataValues() { // Set up the adapter to be used for this test. - TestModule.testAdapterFactory = { fragment -> - createMultiViewTypeWithDataBindingBindableAdapter(lifecycleOwner = fragment) - } + TestModule.testAdapterFactory = + { _, multiTypeFactory -> createMultiViewTypeWithDataBindingBindableAdapter(multiTypeFactory) } launch(BindableAdapterTestActivity::class.java).use { scenario -> val itemLiveData = MutableLiveData("initial") @@ -522,21 +507,22 @@ class BindableAdapterTest { ApplicationProvider.getApplicationContext().inject(this) } - private fun createSingleViewTypeNoDataBindingBindableAdapter(testFragment: Fragment): - BindableAdapter { - return SingleTypeBuilder - .Factory(testFragment).create() - .registerViewBinder( - inflateView = this::inflateTextViewForStringWithoutDataBinding, - bindView = this::bindTextViewForStringWithoutDataBinding - ) - .build() - } + private fun createSingleViewTypeNoDataBindingBindableAdapter( + singleTypeBuilder: SingleTypeBuilder.Factory + ): BindableAdapter { + return singleTypeBuilder.create() + .registerViewBinder( + inflateView = this::inflateTextViewForStringWithoutDataBinding, + bindView = this::bindTextViewForStringWithoutDataBinding + ) + .build() + } - private fun createSingleViewTypeWithDataBindingBindableAdapter(testFragment: Fragment): + private fun createSingleViewTypeWithDataBindingBindableAdapter( + singleTypeBuilder: SingleTypeBuilder.Factory + ): BindableAdapter { - return SingleTypeBuilder - .Factory(testFragment).create() + return singleTypeBuilder.create() .registerViewDataBinderWithSameModelType( inflateDataBinding = TestTextViewForStringWithDataBindingBinding::inflate, setViewModel = TestTextViewForStringWithDataBindingBinding::setViewModel @@ -545,11 +531,9 @@ class BindableAdapterTest { } private fun createSingleViewTypeWithDataBindingAndLiveDataAdapter( - lifecycleOwner: Fragment + singleTypeBuilder: SingleTypeBuilder.Factory ): BindableAdapter { - return SingleTypeBuilder - .Factory(lifecycleOwner).create() - .setLifecycleOwner(lifecycleOwner) + return singleTypeBuilder.create() .registerViewDataBinderWithSameModelType( inflateDataBinding = TestTextViewForLiveDataWithDataBindingBinding::inflate, setViewModel = TestTextViewForLiveDataWithDataBindingBinding::setViewModel @@ -557,10 +541,16 @@ class BindableAdapterTest { .build() } - private fun createMultiViewTypeNoDataBindingBindableAdapter(testFragment: Fragment): + private fun createSingleAdapterWithoutView(singleTypeBuilder: SingleTypeBuilder.Factory): + BindableAdapter { + return singleTypeBuilder.create().build() + } + + private fun createMultiViewTypeNoDataBindingBindableAdapter( + multiTypeBuilder: MultiTypeBuilder.Factory + ): BindableAdapter { - return MultiTypeBuilder - .Factory(testFragment).create(ViewModelType.Companion::deriveTypeFrom) + return multiTypeBuilder.create(ViewModelType.Companion::deriveTypeFrom) .registerViewBinder( viewType = ViewModelType.STRING, inflateView = this::inflateTextViewForStringWithoutDataBinding, @@ -575,11 +565,9 @@ class BindableAdapterTest { } private fun createMultiViewTypeWithDataBindingBindableAdapter( - lifecycleOwner: Fragment + multiTypeBuilder: MultiTypeBuilder.Factory ): BindableAdapter { - return MultiTypeBuilder - .Factory(lifecycleOwner).create(ViewModelType.Companion::deriveTypeFrom) - .setLifecycleOwner(lifecycleOwner) + return multiTypeBuilder.create(ViewModelType.Companion::deriveTypeFrom) .registerViewDataBinderWithSameModelType( viewType = ViewModelType.STRING, inflateDataBinding = TestTextViewForStringWithDataBindingBinding::inflate, @@ -670,7 +658,10 @@ class BindableAdapterTest { class TestModule { companion object { // TODO(#1720): Move this to a test-level binding to avoid the need for static state. - var testAdapterFactory: ((Fragment) -> BindableAdapter)? = null + var testAdapterFactory: ( + (SingleTypeBuilder.Factory, MultiTypeBuilder.Factory) -> + BindableAdapter + )? = null } @Provides @@ -679,8 +670,11 @@ class BindableAdapterTest { "The test adapter factory hasn't been initialized in the test" } return object : BindableAdapterTestFragmentPresenter.BindableAdapterFactory { - override fun create(fragment: Fragment): BindableAdapter { - return createFunction(fragment) + override fun create( + singleTypeBuilder: SingleTypeBuilder.Factory, + multiTypeBuilder: MultiTypeBuilder.Factory + ): BindableAdapter { + return createFunction(singleTypeBuilder, multiTypeBuilder) } } } From 6fb1198d9084f7fd03ad2c6931a4001afb3e4d39 Mon Sep 17 00:00:00 2001 From: kevingitonga Date: Thu, 21 Jul 2022 12:55:04 +0300 Subject: [PATCH 16/34] Fix issues as advised by code review. --- .../app/fragment/FragmentComponentImpl.kt | 2 + .../promotedlist/PromotedStoryListView.kt | 50 +++---- .../state/StatePlayerRecyclerViewAssembler.kt | 2 +- .../app/recyclerview/BindableAdapter.kt | 20 +-- .../testing/DragDropTestActivityPresenter.kt | 56 ++------ .../app/testing/DragDropTestFragment.kt | 57 ++++++++ .../testing/DragDropTestFragmentPresenter.kt | 76 ++++++++++ .../res/layout/drag_drop_test_activity.xml | 10 +- .../res/layout/drag_drop_test_fragment.xml | 10 ++ .../app/recyclerview/BindableAdapterTest.kt | 130 ------------------ 10 files changed, 188 insertions(+), 225 deletions(-) create mode 100644 app/src/main/java/org/oppia/android/app/testing/DragDropTestFragment.kt create mode 100644 app/src/main/java/org/oppia/android/app/testing/DragDropTestFragmentPresenter.kt create mode 100644 app/src/main/res/layout/drag_drop_test_fragment.xml diff --git a/app/src/main/java/org/oppia/android/app/fragment/FragmentComponentImpl.kt b/app/src/main/java/org/oppia/android/app/fragment/FragmentComponentImpl.kt index 5ec6f40e761..71688e3a4db 100644 --- a/app/src/main/java/org/oppia/android/app/fragment/FragmentComponentImpl.kt +++ b/app/src/main/java/org/oppia/android/app/fragment/FragmentComponentImpl.kt @@ -61,6 +61,7 @@ import org.oppia.android.app.settings.profile.ProfileResetPinFragment import org.oppia.android.app.shim.IntentFactoryShimModule import org.oppia.android.app.shim.ViewBindingShimModule import org.oppia.android.app.story.StoryFragment +import org.oppia.android.app.testing.DragDropTestFragment import org.oppia.android.app.testing.ExplorationTestActivityPresenter import org.oppia.android.app.testing.ImageRegionSelectionTestFragment import org.oppia.android.app.topic.TopicFragment @@ -167,4 +168,5 @@ interface FragmentComponentImpl : FragmentComponent, ViewComponentBuilderInjecto fun inject(walkthroughFinalFragment: WalkthroughFinalFragment) fun inject(walkthroughTopicListFragment: WalkthroughTopicListFragment) fun inject(walkthroughWelcomeFragment: WalkthroughWelcomeFragment) + fun inject(dragDropTestFragment: DragDropTestFragment) } diff --git a/app/src/main/java/org/oppia/android/app/home/promotedlist/PromotedStoryListView.kt b/app/src/main/java/org/oppia/android/app/home/promotedlist/PromotedStoryListView.kt index b01e0c6c7c7..b3d3b1ac15f 100644 --- a/app/src/main/java/org/oppia/android/app/home/promotedlist/PromotedStoryListView.kt +++ b/app/src/main/java/org/oppia/android/app/home/promotedlist/PromotedStoryListView.kt @@ -32,46 +32,31 @@ class PromotedStoryListView @JvmOverloads constructor( @Inject lateinit var oppiaLogger: OppiaLogger - @Inject - lateinit var fragment: Fragment - @Inject lateinit var singleTypeBuilderFactory: BindableAdapter.SingleTypeBuilder.Factory lateinit var promotedDataList: List override fun onAttachedToWindow() { - try { - super.onAttachedToWindow() - val viewComponentFactory = - FragmentManager.findFragment(this) as ViewComponentFactory - val viewComponent = viewComponentFactory.createViewComponent(this) as ViewComponentImpl - viewComponent.inject(this) - - // The StartSnapHelper is used to snap between items rather than smooth scrolling, so that - // the item is completely visible in [HomeFragment] as soon as learner lifts the finger - // after scrolling. - val snapHelper = StartSnapHelper() - onFlingListener = null - snapHelper.attachToRecyclerView(this) + super.onAttachedToWindow() + val viewComponentFactory = + FragmentManager.findFragment(this) as ViewComponentFactory + val viewComponent = viewComponentFactory.createViewComponent(this) as ViewComponentImpl + viewComponent.inject(this) - checkIfComponentsInitialized() - } catch (e: IllegalStateException) { - if (::oppiaLogger.isInitialized) { - oppiaLogger.e( - "PromotedStoryListView", - "Throws exception on attach to window", - e - ) - } - } + // The StartSnapHelper is used to snap between items rather than smooth scrolling, so that + // the item is completely visible in [HomeFragment] as soon as learner lifts the finger + // after scrolling. + val snapHelper = StartSnapHelper() + onFlingListener = null + snapHelper.attachToRecyclerView(this) + maybeInitializeAdapter() } - private fun checkIfComponentsInitialized() { + private fun maybeInitializeAdapter() { if (::bindingInterface.isInitialized && ::bindingInterface.isInitialized && ::oppiaLogger.isInitialized && - ::fragment.isInitialized && ::singleTypeBuilderFactory.isInitialized && ::promotedDataList.isInitialized ) { @@ -87,7 +72,7 @@ class PromotedStoryListView @JvmOverloads constructor( fun setPromotedStoryList(newDataList: List?) { if (newDataList != null) { promotedDataList = newDataList - checkIfComponentsInitialized() + maybeInitializeAdapter() } } @@ -100,11 +85,8 @@ class PromotedStoryListView @JvmOverloads constructor( if (adapter == null) { adapter = createAdapter() } - if (::promotedDataList.isInitialized) { - (adapter as BindableAdapter<*>).setDataUnchecked(promotedDataList) - } else { - oppiaLogger.w(PROMOTED_STORY_LIST_VIEW_TAG, "Failed to resolve new story list data") - } + + (adapter as BindableAdapter<*>).setDataUnchecked(promotedDataList) } private fun createAdapter(): BindableAdapter { diff --git a/app/src/main/java/org/oppia/android/app/player/state/StatePlayerRecyclerViewAssembler.kt b/app/src/main/java/org/oppia/android/app/player/state/StatePlayerRecyclerViewAssembler.kt index b30a6843920..16ae481159a 100644 --- a/app/src/main/java/org/oppia/android/app/player/state/StatePlayerRecyclerViewAssembler.kt +++ b/app/src/main/java/org/oppia/android/app/player/state/StatePlayerRecyclerViewAssembler.kt @@ -892,7 +892,7 @@ class StatePlayerRecyclerViewAssembler private constructor( private val singleAdapterFactory: BindableAdapter.SingleTypeBuilder.Factory ) { - private var adapterBuilder: BindableAdapter.MultiTypeBuilder = adapterBuilderFactory.create { it.viewType } /** diff --git a/app/src/main/java/org/oppia/android/app/recyclerview/BindableAdapter.kt b/app/src/main/java/org/oppia/android/app/recyclerview/BindableAdapter.kt index fa1a036e9ee..6fa8b905060 100644 --- a/app/src/main/java/org/oppia/android/app/recyclerview/BindableAdapter.kt +++ b/app/src/main/java/org/oppia/android/app/recyclerview/BindableAdapter.kt @@ -99,13 +99,13 @@ class BindableAdapter internal constructor( * The base builder for [BindableAdapter]. This class should not be used directly--use either * [SingleTypeBuilder] or [MultiTypeBuilder] instead. */ - abstract class BaseBuilder(fragment: Fragment) { + abstract class BaseBuilder(private val fragment: Fragment) { /** * A [WeakReference] to a [LifecycleOwner] for databinding inflation. * Note that this needs to be a weak reference so that long-held references to the adapter do * not potentially leak lifecycle owners (such as fragments and activities). */ - private var lifecycleOwnerRef: WeakReference = WeakReference(fragment) + private val lifecycleOwnerRef: WeakReference = WeakReference(fragment) /** * Returns the [LifecycleOwner] bound to this adapter, or null if there isn't one. This method @@ -206,6 +206,8 @@ class BindableAdapter internal constructor( object : BindableViewHolder(binding.root) { override fun bind(data: T) { setViewModel(binding, data) + // Attach lifecycleOwner after viewModel has been attached to the view. + // Attaching lifecycleOwner before will cause a null pointer exception crash on [HomeFragment] binding.lifecycleOwner = getLifecycleOwner() } } @@ -223,13 +225,11 @@ class BindableAdapter internal constructor( ) } - /** Fragment injectable factory to create new [SingleTypeBuilder] */ + /** Fragment injectable factory to create new [SingleTypeBuilder]. */ class Factory @Inject constructor( val fragment: Fragment ) { - /** - * Returns a new [SingleTypeBuilder] for the specified Data class type. - */ + /**Returns a new [SingleTypeBuilder] for the specified Data class type.*/ inline fun create(): SingleTypeBuilder { return SingleTypeBuilder(T::class, fragment) } @@ -334,6 +334,8 @@ class BindableAdapter internal constructor( object : BindableViewHolder(binding.root) { override fun bind(data: T) { setViewModel(binding, transformViewModel(data)) + // Attach lifecycleOwner after viewModel has been attached to the view. + // Attaching lifecycleOwner before will cause a null pointer exception crash on [HomeFragment] binding.lifecycleOwner = getLifecycleOwner() } } @@ -359,14 +361,12 @@ class BindableAdapter internal constructor( ) } - /** Fragment injectable factory to create new [MultiTypeBuilder] */ + /** Fragment injectable factory to create new [MultiTypeBuilder]. */ class Factory @Inject constructor( val fragment: Fragment ) { - /** - * Returns a new [MultiTypeBuilder] for the specified data class type. - */ + /*** Returns a new [MultiTypeBuilder] for the specified data class type.*/ inline fun > create( noinline computeViewType: ComputeViewType ): MultiTypeBuilder { diff --git a/app/src/main/java/org/oppia/android/app/testing/DragDropTestActivityPresenter.kt b/app/src/main/java/org/oppia/android/app/testing/DragDropTestActivityPresenter.kt index 631383ce55e..6eb6833cbba 100644 --- a/app/src/main/java/org/oppia/android/app/testing/DragDropTestActivityPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/testing/DragDropTestActivityPresenter.kt @@ -1,61 +1,27 @@ package org.oppia.android.app.testing -import android.view.LayoutInflater -import android.view.ViewGroup -import android.widget.TextView import androidx.appcompat.app.AppCompatActivity -import androidx.fragment.app.Fragment -import androidx.recyclerview.widget.RecyclerView import org.oppia.android.R -import org.oppia.android.app.recyclerview.BindableAdapter import javax.inject.Inject /** The presenter for [DragDropTestActivity] */ class DragDropTestActivityPresenter @Inject constructor(private val activity: AppCompatActivity) { - var dataList = mutableListOf("Item 1", "Item 2", "Item 3", "Item 4") - fun handleOnCreate() { activity.setContentView(R.layout.drag_drop_test_activity) - activity.findViewById(R.id.drag_drop_recycler_view).apply { - adapter = createBindableAdapter() - (adapter as BindableAdapter<*>).setDataUnchecked(dataList) + if (getDragDropTestFragment() == null) { + activity.supportFragmentManager.beginTransaction().add( + R.id.drag_drop_test_fragment_placeholder, + DragDropTestFragment.newInstance() + ).commitNow() } } - private fun createBindableAdapter(): BindableAdapter { - return BindableAdapter.SingleTypeBuilder - .Factory(Fragment()).create() - .registerViewBinder( - inflateView = this::inflateTextViewForStringWithoutDataBinding, - bindView = this::bindTextViewForStringWithoutDataBinding - ) - .build() - } - - private fun bindTextViewForStringWithoutDataBinding(textView: TextView, data: String) { - textView.text = data - } - - private fun inflateTextViewForStringWithoutDataBinding(viewGroup: ViewGroup): TextView { - val inflater = LayoutInflater.from(activity) - return inflater.inflate( - R.layout.test_text_view_for_string_no_data_binding, viewGroup, /* attachToRoot= */ false - ) as TextView - } - - fun onItemDragged( - indexFrom: Int, - indexTo: Int, - adapter: RecyclerView.Adapter - ) { - val item = dataList[indexFrom] - dataList.removeAt(indexFrom) - dataList.add(indexTo, item) - adapter.notifyItemMoved(indexFrom, indexTo) - } - - fun onDragEnded(adapter: RecyclerView.Adapter) { - (adapter as BindableAdapter<*>).setDataUnchecked(dataList) + private fun getDragDropTestFragment(): DragDropTestFragment? { + return activity + .supportFragmentManager + .findFragmentById( + R.id.drag_drop_test_fragment_placeholder + ) as DragDropTestFragment? } } diff --git a/app/src/main/java/org/oppia/android/app/testing/DragDropTestFragment.kt b/app/src/main/java/org/oppia/android/app/testing/DragDropTestFragment.kt new file mode 100644 index 00000000000..6f16e0d7675 --- /dev/null +++ b/app/src/main/java/org/oppia/android/app/testing/DragDropTestFragment.kt @@ -0,0 +1,57 @@ +package org.oppia.android.app.testing + +import android.content.Context +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import org.oppia.android.app.fragment.FragmentComponentImpl +import org.oppia.android.app.fragment.InjectableFragment +import org.oppia.android.app.recyclerview.OnDragEndedListener +import org.oppia.android.app.recyclerview.OnItemDragListener +import org.oppia.android.app.story.StoryFragment +import javax.inject.Inject + +/** Fragment for displaying a DragDropTestFragment. */ +class DragDropTestFragment : InjectableFragment(), OnItemDragListener, OnDragEndedListener { + + companion object { + /** Returns a new [StoryFragment] to display the story corresponding to the specified story ID. */ + fun newInstance(): DragDropTestFragment { + return DragDropTestFragment() + } + } + + @Inject + lateinit var dragDropTestFragmentPresenter: DragDropTestFragmentPresenter + + override fun onAttach(context: Context) { + super.onAttach(context) + (fragmentComponent as FragmentComponentImpl).inject(this) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + + return dragDropTestFragmentPresenter.handleCreateView( + inflater, + container + ) + } + + override fun onDragEnded(adapter: RecyclerView.Adapter) { + dragDropTestFragmentPresenter.onDragEnded(adapter) + } + + override fun onItemDragged( + indexFrom: Int, + indexTo: Int, + adapter: RecyclerView.Adapter + ) { + dragDropTestFragmentPresenter.onItemDragged(indexFrom, indexTo, adapter) + } +} diff --git a/app/src/main/java/org/oppia/android/app/testing/DragDropTestFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/testing/DragDropTestFragmentPresenter.kt new file mode 100644 index 00000000000..f5daf1c2804 --- /dev/null +++ b/app/src/main/java/org/oppia/android/app/testing/DragDropTestFragmentPresenter.kt @@ -0,0 +1,76 @@ +package org.oppia.android.app.testing + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.appcompat.app.AppCompatActivity +import androidx.recyclerview.widget.RecyclerView +import org.oppia.android.R +import org.oppia.android.app.recyclerview.BindableAdapter +import org.oppia.android.databinding.DragDropTestFragmentBinding +import javax.inject.Inject + +/** The presenter for [DragDropTestFragment]. */ +class DragDropTestFragmentPresenter @Inject constructor( + private val activity: AppCompatActivity, + private val singleTypeBuilder: BindableAdapter.SingleTypeBuilder.Factory +) { + + var dataList = mutableListOf("Item 1", "Item 2", "Item 3", "Item 4") + private lateinit var binding: DragDropTestFragmentBinding + + fun handleCreateView( + inflater: LayoutInflater, + container: ViewGroup? + ): View? { + + binding = DragDropTestFragmentBinding.inflate( + inflater, + container, + /* attachToRoot= */ false + ) + + binding.dragDropRecyclerView.apply { + adapter = createBindableAdapter() + (adapter as BindableAdapter<*>).setDataUnchecked(dataList) + } + + return binding.root + } + + private fun createBindableAdapter(): BindableAdapter { + return singleTypeBuilder.create() + .registerViewBinder( + inflateView = this::inflateTextViewForStringWithoutDataBinding, + bindView = this::bindTextViewForStringWithoutDataBinding + ) + .build() + } + + private fun bindTextViewForStringWithoutDataBinding(textView: TextView, data: String) { + textView.text = data + } + + private fun inflateTextViewForStringWithoutDataBinding(viewGroup: ViewGroup): TextView { + val inflater = LayoutInflater.from(activity) + return inflater.inflate( + R.layout.test_text_view_for_string_no_data_binding, viewGroup, /* attachToRoot= */ false + ) as TextView + } + + fun onItemDragged( + indexFrom: Int, + indexTo: Int, + adapter: RecyclerView.Adapter + ) { + val item = dataList[indexFrom] + dataList.removeAt(indexFrom) + dataList.add(indexTo, item) + adapter.notifyItemMoved(indexFrom, indexTo) + } + + fun onDragEnded(adapter: RecyclerView.Adapter) { + (adapter as BindableAdapter<*>).setDataUnchecked(dataList) + } +} diff --git a/app/src/main/res/layout/drag_drop_test_activity.xml b/app/src/main/res/layout/drag_drop_test_activity.xml index 1a2f89f87d8..beea21df288 100644 --- a/app/src/main/res/layout/drag_drop_test_activity.xml +++ b/app/src/main/res/layout/drag_drop_test_activity.xml @@ -1,7 +1,7 @@ - + android:layout_height="match_parent" + tools:context=".app.story.StoryActivity" /> \ No newline at end of file diff --git a/app/src/main/res/layout/drag_drop_test_fragment.xml b/app/src/main/res/layout/drag_drop_test_fragment.xml new file mode 100644 index 00000000000..4bebd18591f --- /dev/null +++ b/app/src/main/res/layout/drag_drop_test_fragment.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/sharedTest/java/org/oppia/android/app/recyclerview/BindableAdapterTest.kt b/app/src/sharedTest/java/org/oppia/android/app/recyclerview/BindableAdapterTest.kt index 7c0ca780cf0..2ee483ad178 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/recyclerview/BindableAdapterTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/recyclerview/BindableAdapterTest.kt @@ -96,7 +96,6 @@ import org.oppia.android.domain.topic.PrimeTopicAssetsControllerModule import org.oppia.android.domain.workmanager.WorkManagerConfigurationModule import org.oppia.android.testing.OppiaTestRule import org.oppia.android.testing.TestLogReportingModule -import org.oppia.android.testing.assertThrows import org.oppia.android.testing.junit.InitializeDefaultLocaleRule import org.oppia.android.testing.robolectric.RobolectricModule import org.oppia.android.testing.threading.TestCoroutineDispatchers @@ -374,135 +373,6 @@ class BindableAdapterTest { } } - @Test - fun testSingleTypeAdapter_setLifecycleOwnerTwice_throwsException() { - val exception = assertThrows(IllegalStateException::class) { - TestModule.testAdapterFactory = - { singleTypeAdapter, _ -> createSingleAdapterWithoutView(singleTypeAdapter) } - } - - assertThat(exception).hasMessageThat().contains("lifecycle owner has already been bound") - } - - @Test - fun testMultiTypeAdapter_setLifecycleOwnerTwice_throwsException() { - - val exception = assertThrows(IllegalStateException::class) { - TestModule.testAdapterFactory = - { _, multiTypeFactory -> createMultiViewTypeNoDataBindingBindableAdapter(multiTypeFactory) } - } - - assertThat(exception).hasMessageThat().contains("lifecycle owner has already been bound") - } - - @Test - fun testSingleTypeAdapter_withLiveData_noLifecycleOwner_doesNotRebindLiveDataValues() { - // Set up the adapter to be used for this test. - TestModule.testAdapterFactory = { singleTypeFactory, _ -> - createSingleViewTypeWithDataBindingAndLiveDataAdapter(singleTypeFactory) - } - - launch(BindableAdapterTestActivity::class.java).use { scenario -> - val itemLiveData = MutableLiveData("initial") - scenario.onActivity { activity -> - val liveData = getRecyclerViewListLiveData(activity) - liveData.value = listOf(LiveDataModel(itemLiveData)) - } - testCoroutineDispatchers.runCurrent() - - itemLiveData.postValue("new value") - testCoroutineDispatchers.runCurrent() - - // Verify that the bound data did not change despite the underlying live data changing. - onView(atPosition(recyclerViewId = R.id.test_recycler_view, position = 0)).check( - matches( - withText("initial") - ) - ) - } - } - - @Test - fun testSingleTypeAdapter_withLiveData_withLifecycleOwner_rebindsLiveDataValues() { - // Set up the adapter to be used for this test. - TestModule.testAdapterFactory = { singleTypeFactory, _ -> - createSingleViewTypeWithDataBindingAndLiveDataAdapter(singleTypeFactory) - } - - launch(BindableAdapterTestActivity::class.java).use { scenario -> - val itemLiveData = MutableLiveData("initial") - scenario.onActivity { activity -> - val liveData = getRecyclerViewListLiveData(activity) - liveData.value = listOf(LiveDataModel(itemLiveData)) - } - testCoroutineDispatchers.runCurrent() - - itemLiveData.postValue("new value") - testCoroutineDispatchers.runCurrent() - - // The updated live data value should be reflected on the UI due to the bound lifecycle owner. - onView( - atPosition( - recyclerViewId = R.id.test_recycler_view, - position = 0 - ) - ).check(matches(withText("new value"))) - } - } - - @Test - fun testMultiTypeAdapter_withLiveData_noLifecycleOwner_doesNotRebindLiveDataValues() { - // Set up the adapter to be used for this test. - TestModule.testAdapterFactory = - { _, multiTypeFactory -> createMultiViewTypeWithDataBindingBindableAdapter(multiTypeFactory) } - - launch(BindableAdapterTestActivity::class.java).use { scenario -> - val itemLiveData = MutableLiveData("initial") - scenario.onActivity { activity -> - val liveData = getRecyclerViewListLiveData(activity) - liveData.value = listOf(LiveDataModel(itemLiveData)) - } - testCoroutineDispatchers.runCurrent() - - itemLiveData.postValue("new value") - testCoroutineDispatchers.runCurrent() - - // Verify that the bound data did not change despite the underlying live data changing. - onView(atPosition(recyclerViewId = R.id.test_recycler_view, position = 0)).check( - matches( - withText("initial") - ) - ) - } - } - - @Test - fun testMultiTypeAdapter_withLiveData_withLifecycleOwner_rebindsLiveDataValues() { - // Set up the adapter to be used for this test. - TestModule.testAdapterFactory = - { _, multiTypeFactory -> createMultiViewTypeWithDataBindingBindableAdapter(multiTypeFactory) } - - launch(BindableAdapterTestActivity::class.java).use { scenario -> - val itemLiveData = MutableLiveData("initial") - scenario.onActivity { activity -> - val liveData = getRecyclerViewListLiveData(activity) - liveData.value = listOf(LiveDataModel(itemLiveData)) - } - testCoroutineDispatchers.runCurrent() - - itemLiveData.postValue("new value") - testCoroutineDispatchers.runCurrent() - - // The updated live data value should be reflected on the UI due to the bound lifecycle owner. - onView( - atPosition( - recyclerViewId = R.id.test_recycler_view, - position = 0 - ) - ).check(matches(withText("new value"))) - } - } - private fun setUpTestApplicationComponent() { ApplicationProvider.getApplicationContext().inject(this) } From 8699b2b826e182b09e6cce4cff0bcdfc57d27df7 Mon Sep 17 00:00:00 2001 From: kevingitonga Date: Thu, 21 Jul 2022 16:03:26 +0300 Subject: [PATCH 17/34] Fix issue causing some of failing tests. --- .../app/testing/DragDropTestActivity.kt | 20 +------------------ .../app/testing/DragDropTestActivityTest.kt | 9 +++++---- 2 files changed, 6 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/org/oppia/android/app/testing/DragDropTestActivity.kt b/app/src/main/java/org/oppia/android/app/testing/DragDropTestActivity.kt index 4d491cf0615..0961e6c9343 100644 --- a/app/src/main/java/org/oppia/android/app/testing/DragDropTestActivity.kt +++ b/app/src/main/java/org/oppia/android/app/testing/DragDropTestActivity.kt @@ -1,18 +1,12 @@ package org.oppia.android.app.testing import android.os.Bundle -import androidx.recyclerview.widget.RecyclerView import org.oppia.android.app.activity.ActivityComponentImpl import org.oppia.android.app.activity.InjectableAppCompatActivity -import org.oppia.android.app.recyclerview.OnDragEndedListener -import org.oppia.android.app.recyclerview.OnItemDragListener import javax.inject.Inject /** Test Activity used for testing [DragAndDropItemFacilitator] functionality */ -class DragDropTestActivity : - InjectableAppCompatActivity(), - OnItemDragListener, - OnDragEndedListener { +class DragDropTestActivity : InjectableAppCompatActivity() { @Inject lateinit var dragDropTestActivityPresenter: DragDropTestActivityPresenter @@ -22,16 +16,4 @@ class DragDropTestActivity : (activityComponent as ActivityComponentImpl).inject(this) dragDropTestActivityPresenter.handleOnCreate() } - - override fun onItemDragged( - indexFrom: Int, - indexTo: Int, - adapter: RecyclerView.Adapter - ) { - dragDropTestActivityPresenter.onItemDragged(indexFrom, indexTo, adapter) - } - - override fun onDragEnded(adapter: RecyclerView.Adapter) { - dragDropTestActivityPresenter.onDragEnded(adapter) - } } diff --git a/app/src/sharedTest/java/org/oppia/android/app/testing/DragDropTestActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/testing/DragDropTestActivityTest.kt index 0d3c55261f1..f647573572d 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/testing/DragDropTestActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/testing/DragDropTestActivityTest.kt @@ -160,10 +160,11 @@ class DragDropTestActivityTest { @Test fun testDragDropTestActivity_dragItem3ToPosition2() { - launch(DragDropTestActivity::class.java).use { scenario -> - scenario.onActivity { activity -> - attachDragDropToActivity(activity) - } + launch(DragDropTestActivity::class.java).use { + // scenario -> + // scenario.onActivity { activity -> + // attachDragDropToActivity(activity) + // } onView(withId(R.id.drag_drop_recycler_view)) onView(withId(R.id.drag_drop_recycler_view)).perform( DragViewAction( From 912bdcd7fd86dea20b301e269dd443559c9456cf Mon Sep 17 00:00:00 2001 From: kevingitonga Date: Fri, 22 Jul 2022 13:21:07 +0300 Subject: [PATCH 18/34] Fix issue causing some of failing tests. --- .../promotedlist/ComingSoonTopicsListView.kt | 33 +++++++++++++++--- .../state/DragDropSortInteractionView.kt | 34 ++++++++++++++++--- .../player/state/SelectionInteractionView.kt | 20 ++++++++++- 3 files changed, 76 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/org/oppia/android/app/home/promotedlist/ComingSoonTopicsListView.kt b/app/src/main/java/org/oppia/android/app/home/promotedlist/ComingSoonTopicsListView.kt index 04239be8b94..3610ffe93df 100644 --- a/app/src/main/java/org/oppia/android/app/home/promotedlist/ComingSoonTopicsListView.kt +++ b/app/src/main/java/org/oppia/android/app/home/promotedlist/ComingSoonTopicsListView.kt @@ -35,6 +35,8 @@ class ComingSoonTopicsListView @JvmOverloads constructor( @Inject lateinit var singleTypeAdapterFactory: BindableAdapter.SingleTypeBuilder.Factory + lateinit var comingSoonDataList: List + override fun onAttachedToWindow() { super.onAttachedToWindow() @@ -48,6 +50,18 @@ class ComingSoonTopicsListView @JvmOverloads constructor( val snapHelper = StartSnapHelper() onFlingListener = null snapHelper.attachToRecyclerView(this) + maybeInitializeAdapter() + } + + private fun maybeInitializeAdapter() { + if (::bindingInterface.isInitialized && + ::bindingInterface.isInitialized && + ::oppiaLogger.isInitialized && + ::singleTypeAdapterFactory.isInitialized && + ::comingSoonDataList.isInitialized + ) { + bindDataToAdapter() + } } /** @@ -61,14 +75,23 @@ class ComingSoonTopicsListView @JvmOverloads constructor( // way to check that the adapter is created. // This ensures that the adapter will only be created once and correctly rebinds the data. // For more context: https://github.com/oppia/oppia-android/pull/2246#pullrequestreview-565964462 + if (newDataList != null) { + comingSoonDataList = newDataList + maybeInitializeAdapter() + } + } + + private fun bindDataToAdapter() { + // To reliably bind data only after the adapter is created, we manually set the data so we can first + // check for the adapter; when using an existing [RecyclerViewBindingAdapter] there is no reliable + // way to check that the adapter is created. + // This ensures that the adapter will only be created once and correctly rebinds the data. + // For more context: https://github.com/oppia/oppia-android/pull/2246#pullrequestreview-565964462 if (adapter == null) { adapter = createAdapter() } - if (newDataList == null) { - oppiaLogger.w(COMING_SOON_TOPIC_LIST_VIEW_TAG, "Failed to resolve upcoming topic list data") - } else { - (adapter as BindableAdapter<*>).setDataUnchecked(newDataList) - } + + (adapter as BindableAdapter<*>).setDataUnchecked(comingSoonDataList) } private fun createAdapter(): BindableAdapter { diff --git a/app/src/main/java/org/oppia/android/app/player/state/DragDropSortInteractionView.kt b/app/src/main/java/org/oppia/android/app/player/state/DragDropSortInteractionView.kt index 289bcc69301..635beab6c07 100644 --- a/app/src/main/java/org/oppia/android/app/player/state/DragDropSortInteractionView.kt +++ b/app/src/main/java/org/oppia/android/app/player/state/DragDropSortInteractionView.kt @@ -62,12 +62,31 @@ class DragDropSortInteractionView @JvmOverloads constructor( override fun onAttachedToWindow() { super.onAttachedToWindow() - - val viewComponentFactory = FragmentManager.findFragment(this) as ViewComponentFactory + val viewComponentFactory = + FragmentManager.findFragment(this) as ViewComponentFactory val viewComponent = viewComponentFactory.createViewComponent(this) as ViewComponentImpl viewComponent.inject(this) isAccessibilityEnabled = accessibilityService.isScreenReaderEnabled() + maybeInitializeAdapter() + } + + private fun maybeInitializeAdapter() { + if (::htmlParserFactory.isInitialized && + ::accessibilityService.isInitialized && + ::entityType.isInitialized && + ::resourceBucketName.isInitialized && + ::viewBindingShim.isInitialized && + ::singleTypeBuilderFactory.isInitialized + ) { + bindDataToAdapter() + } + } + + private fun bindDataToAdapter() { + if (adapter == null) { + adapter = createAdapter() + } } fun allowMultipleItemsInSamePosition(isAllowed: Boolean) { @@ -75,13 +94,14 @@ class DragDropSortInteractionView @JvmOverloads constructor( // with setting the adapter data, so this needs to be done in an order-agnostic way. There should be a way to do // this more efficiently and cleanly than always relying on notifying of potential changes in the adapter when the // type is set (plus the type ought to be permanent). + maybeInitializeAdapter() this.isMultipleItemsInSamePositionAllowed = isAllowed - adapter = createAdapter() } // TODO(#264): Clean up HTML parser such that it can be handled completely through a binding adapter, allowing // TextViews that require custom Oppia HTML parsing to be fully automatically bound through data-binding. fun setEntityId(entityId: String) { + maybeInitializeAdapter() this.entityId = entityId } @@ -166,11 +186,15 @@ class DragDropSortInteractionView @JvmOverloads constructor( fun setEntityId( dragDropSortInteractionView: DragDropSortInteractionView, entityId: String -) = dragDropSortInteractionView.setEntityId(entityId) +) { + dragDropSortInteractionView.setEntityId(entityId) +} /** Sets the [SelectionItemInputType] for a specific [SelectionInteractionView] via data-binding. */ @BindingAdapter("allowMultipleItemsInSamePosition") fun setAllowMultipleItemsInSamePosition( dragDropSortInteractionView: DragDropSortInteractionView, isAllowed: Boolean -) = dragDropSortInteractionView.allowMultipleItemsInSamePosition(isAllowed) +) { + dragDropSortInteractionView.allowMultipleItemsInSamePosition(isAllowed) +} diff --git a/app/src/main/java/org/oppia/android/app/player/state/SelectionInteractionView.kt b/app/src/main/java/org/oppia/android/app/player/state/SelectionInteractionView.kt index b7dcf363372..b3707a68bac 100644 --- a/app/src/main/java/org/oppia/android/app/player/state/SelectionInteractionView.kt +++ b/app/src/main/java/org/oppia/android/app/player/state/SelectionInteractionView.kt @@ -57,6 +57,22 @@ class SelectionInteractionView @JvmOverloads constructor( val viewComponentFactory = FragmentManager.findFragment(this) as ViewComponentFactory val viewComponent = viewComponentFactory.createViewComponent(this) as ViewComponentImpl viewComponent.inject(this) + maybeInitializeAdapter() + } + + private fun maybeInitializeAdapter() { + if (::bindingInterface.isInitialized && + ::htmlParserFactory.isInitialized && + ::entityType.isInitialized && + ::resourceBucketName.isInitialized && + ::singleTypeBuilderFactory.isInitialized + ) { + bindDataToAdapter() + } + } + + private fun bindDataToAdapter() { + adapter = createAdapter() } fun setAllOptionsItemInputType(selectionItemInputType: SelectionItemInputType) { @@ -65,13 +81,14 @@ class SelectionInteractionView @JvmOverloads constructor( // this more efficiently and cleanly than always relying on notifying of potential changes in the adapter when the // type is set (plus the type ought to be permanent). this.selectionItemInputType = selectionItemInputType - adapter = createAdapter() + maybeInitializeAdapter() } // TODO(#264): Clean up HTML parser such that it can be handled completely through a binding adapter, allowing // TextViews that require custom Oppia HTML parsing to be fully automatically bound through data-binding. fun setEntityId(entityId: String) { this.entityId = entityId + maybeInitializeAdapter() } /** @@ -81,6 +98,7 @@ class SelectionInteractionView @JvmOverloads constructor( */ fun setWrittenTranslationContext(writtenTranslationContext: WrittenTranslationContext) { this.writtenTranslationContext = writtenTranslationContext + maybeInitializeAdapter() } private fun createAdapter(): BindableAdapter { From 816d62648534c5a31a25a7514d20a0091d59f69d Mon Sep 17 00:00:00 2001 From: kevingitonga Date: Mon, 25 Jul 2022 14:22:44 +0300 Subject: [PATCH 19/34] Revert unused resources, to fix Static check test failure. --- .../app/home/promotedlist/ComingSoonTopicsListView.kt | 4 +++- .../android/app/home/promotedlist/PromotedStoryListView.kt | 5 ++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/oppia/android/app/home/promotedlist/ComingSoonTopicsListView.kt b/app/src/main/java/org/oppia/android/app/home/promotedlist/ComingSoonTopicsListView.kt index 3610ffe93df..2d4b29891d6 100644 --- a/app/src/main/java/org/oppia/android/app/home/promotedlist/ComingSoonTopicsListView.kt +++ b/app/src/main/java/org/oppia/android/app/home/promotedlist/ComingSoonTopicsListView.kt @@ -75,7 +75,9 @@ class ComingSoonTopicsListView @JvmOverloads constructor( // way to check that the adapter is created. // This ensures that the adapter will only be created once and correctly rebinds the data. // For more context: https://github.com/oppia/oppia-android/pull/2246#pullrequestreview-565964462 - if (newDataList != null) { + if (newDataList == null) { + oppiaLogger.w(COMING_SOON_TOPIC_LIST_VIEW_TAG, "Failed to resolve new topics list data") + } else { comingSoonDataList = newDataList maybeInitializeAdapter() } diff --git a/app/src/main/java/org/oppia/android/app/home/promotedlist/PromotedStoryListView.kt b/app/src/main/java/org/oppia/android/app/home/promotedlist/PromotedStoryListView.kt index b3d3b1ac15f..7e26a55e850 100644 --- a/app/src/main/java/org/oppia/android/app/home/promotedlist/PromotedStoryListView.kt +++ b/app/src/main/java/org/oppia/android/app/home/promotedlist/PromotedStoryListView.kt @@ -70,7 +70,10 @@ class PromotedStoryListView @JvmOverloads constructor( * @param newDataList the new list of stories to present */ fun setPromotedStoryList(newDataList: List?) { - if (newDataList != null) { + + if (newDataList == null) { + oppiaLogger.w(PROMOTED_STORY_LIST_VIEW_TAG, "Failed to resolve new topics list data") + } else { promotedDataList = newDataList maybeInitializeAdapter() } From 2d4652abb7063960c5897f8ab1380c6c44a69758 Mon Sep 17 00:00:00 2001 From: kevingitonga Date: Mon, 25 Jul 2022 15:56:07 +0300 Subject: [PATCH 20/34] Add KDocs to implemented public functions, to fix KDocs Static check test failure. --- .../app/home/promotedlist/ComingSoonTopicsListView.kt | 3 +-- .../android/app/home/promotedlist/PromotedStoryListView.kt | 3 +-- .../android/app/testing/DragDropTestFragmentPresenter.kt | 5 ++++- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/org/oppia/android/app/home/promotedlist/ComingSoonTopicsListView.kt b/app/src/main/java/org/oppia/android/app/home/promotedlist/ComingSoonTopicsListView.kt index 2d4b29891d6..2e76b8d1b2e 100644 --- a/app/src/main/java/org/oppia/android/app/home/promotedlist/ComingSoonTopicsListView.kt +++ b/app/src/main/java/org/oppia/android/app/home/promotedlist/ComingSoonTopicsListView.kt @@ -35,7 +35,7 @@ class ComingSoonTopicsListView @JvmOverloads constructor( @Inject lateinit var singleTypeAdapterFactory: BindableAdapter.SingleTypeBuilder.Factory - lateinit var comingSoonDataList: List + private lateinit var comingSoonDataList: List override fun onAttachedToWindow() { super.onAttachedToWindow() @@ -66,7 +66,6 @@ class ComingSoonTopicsListView @JvmOverloads constructor( /** * Sets the list of coming soon topics that this view shows to the learner. - * * @param newDataList the new list of topics to present */ fun setComingSoonTopicList(newDataList: List?) { diff --git a/app/src/main/java/org/oppia/android/app/home/promotedlist/PromotedStoryListView.kt b/app/src/main/java/org/oppia/android/app/home/promotedlist/PromotedStoryListView.kt index 7e26a55e850..58fcd0bbb03 100644 --- a/app/src/main/java/org/oppia/android/app/home/promotedlist/PromotedStoryListView.kt +++ b/app/src/main/java/org/oppia/android/app/home/promotedlist/PromotedStoryListView.kt @@ -35,7 +35,7 @@ class PromotedStoryListView @JvmOverloads constructor( @Inject lateinit var singleTypeBuilderFactory: BindableAdapter.SingleTypeBuilder.Factory - lateinit var promotedDataList: List + private lateinit var promotedDataList: List override fun onAttachedToWindow() { super.onAttachedToWindow() @@ -66,7 +66,6 @@ class PromotedStoryListView @JvmOverloads constructor( /** * Sets the list of promoted stories that this view shows to the learner. - * * @param newDataList the new list of stories to present */ fun setPromotedStoryList(newDataList: List?) { diff --git a/app/src/main/java/org/oppia/android/app/testing/DragDropTestFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/testing/DragDropTestFragmentPresenter.kt index f5daf1c2804..cca40a5a8fc 100644 --- a/app/src/main/java/org/oppia/android/app/testing/DragDropTestFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/testing/DragDropTestFragmentPresenter.kt @@ -17,9 +17,10 @@ class DragDropTestFragmentPresenter @Inject constructor( private val singleTypeBuilder: BindableAdapter.SingleTypeBuilder.Factory ) { - var dataList = mutableListOf("Item 1", "Item 2", "Item 3", "Item 4") + private var dataList = mutableListOf("Item 1", "Item 2", "Item 3", "Item 4") private lateinit var binding: DragDropTestFragmentBinding + /** This handles OnCreateView() of [DragDropTestFragment]. */ fun handleCreateView( inflater: LayoutInflater, container: ViewGroup? @@ -59,6 +60,7 @@ class DragDropTestFragmentPresenter @Inject constructor( ) as TextView } + /** This handles dragging of items from given position in [DragDropTestFragment]. */ fun onItemDragged( indexFrom: Int, indexTo: Int, @@ -70,6 +72,7 @@ class DragDropTestFragmentPresenter @Inject constructor( adapter.notifyItemMoved(indexFrom, indexTo) } + /** This receives dragEndedEvent and unchecks data list in [DragDropTestFragment]. */ fun onDragEnded(adapter: RecyclerView.Adapter) { (adapter as BindableAdapter<*>).setDataUnchecked(dataList) } From be4d3b9bae2803f7093d0005bfa22367cfdeb182 Mon Sep 17 00:00:00 2001 From: kevingitonga Date: Mon, 25 Jul 2022 16:36:24 +0300 Subject: [PATCH 21/34] Add exemption for test to DragDropTestFragmentPresenter and DragDropTestFragment since they are actual test classes. --- scripts/assets/test_file_exemptions.textproto | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/assets/test_file_exemptions.textproto b/scripts/assets/test_file_exemptions.textproto index ad6b4398d00..d57032a273f 100644 --- a/scripts/assets/test_file_exemptions.textproto +++ b/scripts/assets/test_file_exemptions.textproto @@ -413,6 +413,8 @@ exempted_file_path: "app/src/main/java/org/oppia/android/app/testing/HomeTestAct exempted_file_path: "app/src/main/java/org/oppia/android/app/testing/HtmlParserTestActivity.kt" exempted_file_path: "app/src/main/java/org/oppia/android/app/testing/ImageRegionSelectionTestActivity.kt" exempted_file_path: "app/src/main/java/org/oppia/android/app/testing/ImageRegionSelectionTestFragment.kt" +exempted_file_path: "app/src/main/java/org/oppia/android/app/testing/DragDropTestFragmentPresenter.kt" +exempted_file_path: "app/src/main/java/org/oppia/android/app/testing/DragDropTestFragment.kt" exempted_file_path: "app/src/main/java/org/oppia/android/app/testing/ImageRegionSelectionTestFragmentPresenter.kt" exempted_file_path: "app/src/main/java/org/oppia/android/app/testing/ImageViewBindingAdaptersTestActivity.kt" exempted_file_path: "app/src/main/java/org/oppia/android/app/testing/LessonThumbnailImageViewTestActivity.kt" From 4ef20fa99c248632f75d0d004d504c89dbafe056 Mon Sep 17 00:00:00 2001 From: kevingitonga Date: Tue, 26 Jul 2022 15:15:03 +0300 Subject: [PATCH 22/34] Fix nits and updates as advised during code review. --- .../app/recyclerview/BindableAdapter.kt | 12 ++-- .../testing/DragDropTestActivityPresenter.kt | 2 +- .../app/testing/DragDropTestFragment.kt | 11 ++-- .../testing/DragDropTestFragmentPresenter.kt | 2 - .../res/layout/drag_drop_test_activity.xml | 3 +- .../res/layout/drag_drop_test_fragment.xml | 3 +- .../app/recyclerview/BindableAdapterTest.kt | 56 +++++++++++++++++++ .../app/testing/DragDropTestActivityTest.kt | 9 ++- 8 files changed, 78 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/org/oppia/android/app/recyclerview/BindableAdapter.kt b/app/src/main/java/org/oppia/android/app/recyclerview/BindableAdapter.kt index 6fa8b905060..edf508ffe46 100644 --- a/app/src/main/java/org/oppia/android/app/recyclerview/BindableAdapter.kt +++ b/app/src/main/java/org/oppia/android/app/recyclerview/BindableAdapter.kt @@ -99,7 +99,7 @@ class BindableAdapter internal constructor( * The base builder for [BindableAdapter]. This class should not be used directly--use either * [SingleTypeBuilder] or [MultiTypeBuilder] instead. */ - abstract class BaseBuilder(private val fragment: Fragment) { + abstract class BaseBuilder internal constructor(fragment: Fragment) { /** * A [WeakReference] to a [LifecycleOwner] for databinding inflation. * Note that this needs to be a weak reference so that long-held references to the adapter do @@ -130,7 +130,7 @@ class BindableAdapter internal constructor( */ class SingleTypeBuilder( private val dataClassType: KClass, - private val fragment: Fragment + fragment: Fragment ) : BaseBuilder(fragment) { private lateinit var viewHolderFactory: ViewHolderFactory @@ -207,7 +207,7 @@ class BindableAdapter internal constructor( override fun bind(data: T) { setViewModel(binding, data) // Attach lifecycleOwner after viewModel has been attached to the view. - // Attaching lifecycleOwner before will cause a null pointer exception crash on [HomeFragment] + // Attaching lifecycleOwner before view model initialization can sometimes cause a NullPointerException because data might not be attached to the views yet. binding.lifecycleOwner = getLifecycleOwner() } } @@ -245,7 +245,7 @@ class BindableAdapter internal constructor( class MultiTypeBuilder>( private val dataClassType: KClass, private val computeViewType: ComputeViewType, - private val fragment: Fragment + fragment: Fragment ) : BaseBuilder(fragment) { private var viewHolderFactoryMap: MutableMap> = HashMap() @@ -334,8 +334,8 @@ class BindableAdapter internal constructor( object : BindableViewHolder(binding.root) { override fun bind(data: T) { setViewModel(binding, transformViewModel(data)) - // Attach lifecycleOwner after viewModel has been attached to the view. - // Attaching lifecycleOwner before will cause a null pointer exception crash on [HomeFragment] + // Attach lifecycleOwner after viewModel has been attached data to the view. + // Attaching lifecycleOwner before view model initialization can sometimes cause a NullPointerException because data might not be attached to the views yet. binding.lifecycleOwner = getLifecycleOwner() } } diff --git a/app/src/main/java/org/oppia/android/app/testing/DragDropTestActivityPresenter.kt b/app/src/main/java/org/oppia/android/app/testing/DragDropTestActivityPresenter.kt index 6eb6833cbba..1cf39bf624a 100644 --- a/app/src/main/java/org/oppia/android/app/testing/DragDropTestActivityPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/testing/DragDropTestActivityPresenter.kt @@ -22,6 +22,6 @@ class DragDropTestActivityPresenter @Inject constructor(private val activity: Ap .supportFragmentManager .findFragmentById( R.id.drag_drop_test_fragment_placeholder - ) as DragDropTestFragment? + ) as? DragDropTestFragment } } diff --git a/app/src/main/java/org/oppia/android/app/testing/DragDropTestFragment.kt b/app/src/main/java/org/oppia/android/app/testing/DragDropTestFragment.kt index 6f16e0d7675..e14ca41b617 100644 --- a/app/src/main/java/org/oppia/android/app/testing/DragDropTestFragment.kt +++ b/app/src/main/java/org/oppia/android/app/testing/DragDropTestFragment.kt @@ -10,14 +10,18 @@ import org.oppia.android.app.fragment.FragmentComponentImpl import org.oppia.android.app.fragment.InjectableFragment import org.oppia.android.app.recyclerview.OnDragEndedListener import org.oppia.android.app.recyclerview.OnItemDragListener -import org.oppia.android.app.story.StoryFragment import javax.inject.Inject -/** Fragment for displaying a DragDropTestFragment. */ +/** + * Fragment for displaying a [DragDropTestFragment]. + * Added to enable access to [BindableAdapter] + * which should be provided through dependency injection, + * this is replacing use case of [DragDropTestActivity] since adapter can not be injected to activities. + */ class DragDropTestFragment : InjectableFragment(), OnItemDragListener, OnDragEndedListener { companion object { - /** Returns a new [StoryFragment] to display the story corresponding to the specified story ID. */ + /** Returns a new instance of [DragDropTestFragment]. */ fun newInstance(): DragDropTestFragment { return DragDropTestFragment() } @@ -36,7 +40,6 @@ class DragDropTestFragment : InjectableFragment(), OnItemDragListener, OnDragEnd container: ViewGroup?, savedInstanceState: Bundle? ): View? { - return dragDropTestFragmentPresenter.handleCreateView( inflater, container diff --git a/app/src/main/java/org/oppia/android/app/testing/DragDropTestFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/testing/DragDropTestFragmentPresenter.kt index cca40a5a8fc..3e6da0e1e55 100644 --- a/app/src/main/java/org/oppia/android/app/testing/DragDropTestFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/testing/DragDropTestFragmentPresenter.kt @@ -25,7 +25,6 @@ class DragDropTestFragmentPresenter @Inject constructor( inflater: LayoutInflater, container: ViewGroup? ): View? { - binding = DragDropTestFragmentBinding.inflate( inflater, container, @@ -36,7 +35,6 @@ class DragDropTestFragmentPresenter @Inject constructor( adapter = createBindableAdapter() (adapter as BindableAdapter<*>).setDataUnchecked(dataList) } - return binding.root } diff --git a/app/src/main/res/layout/drag_drop_test_activity.xml b/app/src/main/res/layout/drag_drop_test_activity.xml index beea21df288..976b3621f95 100644 --- a/app/src/main/res/layout/drag_drop_test_activity.xml +++ b/app/src/main/res/layout/drag_drop_test_activity.xml @@ -4,4 +4,5 @@ android:id="@+id/drag_drop_test_fragment_placeholder" android:layout_width="match_parent" android:layout_height="match_parent" - tools:context=".app.story.StoryActivity" /> \ No newline at end of file + tools:context=".app.testing.DragDropTestActivity"> + \ No newline at end of file diff --git a/app/src/main/res/layout/drag_drop_test_fragment.xml b/app/src/main/res/layout/drag_drop_test_fragment.xml index 4bebd18591f..27b6d01e463 100644 --- a/app/src/main/res/layout/drag_drop_test_fragment.xml +++ b/app/src/main/res/layout/drag_drop_test_fragment.xml @@ -6,5 +6,6 @@ android:id="@+id/drag_drop_recycler_view" android:layout_width="match_parent" android:layout_height="wrap_content" - app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/> + app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"> + \ No newline at end of file diff --git a/app/src/sharedTest/java/org/oppia/android/app/recyclerview/BindableAdapterTest.kt b/app/src/sharedTest/java/org/oppia/android/app/recyclerview/BindableAdapterTest.kt index 2ee483ad178..2c85a003543 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/recyclerview/BindableAdapterTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/recyclerview/BindableAdapterTest.kt @@ -373,6 +373,62 @@ class BindableAdapterTest { } } + @Test + fun testSingleTypeAdapter_withLiveData_withLifecycleOwner_rebindsLiveDataValues() { + // Set up the adapter to be used for this test. + TestModule.testAdapterFactory = { singleTypeFactory, _ -> + createSingleViewTypeWithDataBindingAndLiveDataAdapter(singleTypeFactory) + } + + launch(BindableAdapterTestActivity::class.java).use { scenario -> + val itemLiveData = MutableLiveData("initial") + scenario.onActivity { activity -> + val liveData = getRecyclerViewListLiveData(activity) + liveData.value = listOf(LiveDataModel(itemLiveData)) + } + testCoroutineDispatchers.runCurrent() + + itemLiveData.postValue("new value") + testCoroutineDispatchers.runCurrent() + + // The updated live data value should be reflected on the UI due to the bound lifecycle owner. + onView( + atPosition( + recyclerViewId = R.id.test_recycler_view, + position = 0 + ) + ).check(matches(withText("new value"))) + } + } + + @Test + fun testMultiTypeAdapter_withLiveData_withLifecycleOwner_rebindsLiveDataValues() { + // Set up the adapter to be used for this test. + TestModule.testAdapterFactory = { _, multiTypeFactory -> + createMultiViewTypeWithDataBindingBindableAdapter(multiTypeFactory) + } + + launch(BindableAdapterTestActivity::class.java).use { scenario -> + val itemLiveData = MutableLiveData("initial") + scenario.onActivity { activity -> + val liveData = getRecyclerViewListLiveData(activity) + liveData.value = listOf(LiveDataModel(itemLiveData)) + } + testCoroutineDispatchers.runCurrent() + + itemLiveData.postValue("new value") + testCoroutineDispatchers.runCurrent() + + // The updated live data value should be reflected on the UI due to the bound lifecycle owner. + onView( + atPosition( + recyclerViewId = R.id.test_recycler_view, + position = 0 + ) + ).check(matches(withText("new value"))) + } + } + private fun setUpTestApplicationComponent() { ApplicationProvider.getApplicationContext().inject(this) } diff --git a/app/src/sharedTest/java/org/oppia/android/app/testing/DragDropTestActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/testing/DragDropTestActivityTest.kt index f647573572d..0d3c55261f1 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/testing/DragDropTestActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/testing/DragDropTestActivityTest.kt @@ -160,11 +160,10 @@ class DragDropTestActivityTest { @Test fun testDragDropTestActivity_dragItem3ToPosition2() { - launch(DragDropTestActivity::class.java).use { - // scenario -> - // scenario.onActivity { activity -> - // attachDragDropToActivity(activity) - // } + launch(DragDropTestActivity::class.java).use { scenario -> + scenario.onActivity { activity -> + attachDragDropToActivity(activity) + } onView(withId(R.id.drag_drop_recycler_view)) onView(withId(R.id.drag_drop_recycler_view)).perform( DragViewAction( From cea5a19935a14c27ffae1e43f0487cd7580b181d Mon Sep 17 00:00:00 2001 From: kevingitonga Date: Wed, 27 Jul 2022 12:15:48 +0300 Subject: [PATCH 23/34] Fix nits and updates as advised during code review. --- .../app/player/state/DragDropSortInteractionView.kt | 13 ++++++++----- .../android/app/recyclerview/BindableAdapter.kt | 10 +++++----- .../android/app/testing/DragDropTestFragment.kt | 7 +------ app/src/main/res/layout/drag_drop_test_activity.xml | 2 +- app/src/main/res/layout/drag_drop_test_fragment.xml | 2 +- 5 files changed, 16 insertions(+), 18 deletions(-) diff --git a/app/src/main/java/org/oppia/android/app/player/state/DragDropSortInteractionView.kt b/app/src/main/java/org/oppia/android/app/player/state/DragDropSortInteractionView.kt index 635beab6c07..9bb18011f5d 100644 --- a/app/src/main/java/org/oppia/android/app/player/state/DragDropSortInteractionView.kt +++ b/app/src/main/java/org/oppia/android/app/player/state/DragDropSortInteractionView.kt @@ -2,6 +2,7 @@ package org.oppia.android.app.player.state import android.content.Context import android.util.AttributeSet +import android.util.Log import android.view.LayoutInflater import androidx.core.view.isVisible import androidx.databinding.BindingAdapter @@ -68,7 +69,9 @@ class DragDropSortInteractionView @JvmOverloads constructor( viewComponent.inject(this) isAccessibilityEnabled = accessibilityService.isScreenReaderEnabled() - maybeInitializeAdapter() + + Log.e("ATTACHING TO WIN", "ATTACHING TO WIN") + // maybeInitializeAdapter() } private fun maybeInitializeAdapter() { @@ -94,14 +97,14 @@ class DragDropSortInteractionView @JvmOverloads constructor( // with setting the adapter data, so this needs to be done in an order-agnostic way. There should be a way to do // this more efficiently and cleanly than always relying on notifying of potential changes in the adapter when the // type is set (plus the type ought to be permanent). - maybeInitializeAdapter() + // maybeInitializeAdapter() this.isMultipleItemsInSamePositionAllowed = isAllowed } // TODO(#264): Clean up HTML parser such that it can be handled completely through a binding adapter, allowing // TextViews that require custom Oppia HTML parsing to be fully automatically bound through data-binding. fun setEntityId(entityId: String) { - maybeInitializeAdapter() + // maybeInitializeAdapter() this.entityId = entityId } @@ -187,7 +190,7 @@ fun setEntityId( dragDropSortInteractionView: DragDropSortInteractionView, entityId: String ) { - dragDropSortInteractionView.setEntityId(entityId) + // dragDropSortInteractionView.setEntityId(entityId) } /** Sets the [SelectionItemInputType] for a specific [SelectionInteractionView] via data-binding. */ @@ -196,5 +199,5 @@ fun setAllowMultipleItemsInSamePosition( dragDropSortInteractionView: DragDropSortInteractionView, isAllowed: Boolean ) { - dragDropSortInteractionView.allowMultipleItemsInSamePosition(isAllowed) + // dragDropSortInteractionView.allowMultipleItemsInSamePosition(isAllowed) } diff --git a/app/src/main/java/org/oppia/android/app/recyclerview/BindableAdapter.kt b/app/src/main/java/org/oppia/android/app/recyclerview/BindableAdapter.kt index edf508ffe46..0d6724fbb38 100644 --- a/app/src/main/java/org/oppia/android/app/recyclerview/BindableAdapter.kt +++ b/app/src/main/java/org/oppia/android/app/recyclerview/BindableAdapter.kt @@ -99,7 +99,7 @@ class BindableAdapter internal constructor( * The base builder for [BindableAdapter]. This class should not be used directly--use either * [SingleTypeBuilder] or [MultiTypeBuilder] instead. */ - abstract class BaseBuilder internal constructor(fragment: Fragment) { + abstract class BaseBuilder(fragment: Fragment) { /** * A [WeakReference] to a [LifecycleOwner] for databinding inflation. * Note that this needs to be a weak reference so that long-held references to the adapter do @@ -206,8 +206,8 @@ class BindableAdapter internal constructor( object : BindableViewHolder(binding.root) { override fun bind(data: T) { setViewModel(binding, data) - // Attach lifecycleOwner after viewModel has been attached to the view. - // Attaching lifecycleOwner before view model initialization can sometimes cause a NullPointerException because data might not be attached to the views yet. + // Attaching lifecycleOwner before view model initialization can sometimes cause a + // NullPointerException because data might not be attached to the views yet. binding.lifecycleOwner = getLifecycleOwner() } } @@ -334,8 +334,8 @@ class BindableAdapter internal constructor( object : BindableViewHolder(binding.root) { override fun bind(data: T) { setViewModel(binding, transformViewModel(data)) - // Attach lifecycleOwner after viewModel has been attached data to the view. - // Attaching lifecycleOwner before view model initialization can sometimes cause a NullPointerException because data might not be attached to the views yet. + // Attaching lifecycleOwner before view model initialization can sometimes cause a + // NullPointerException because data might not be attached to the views yet. binding.lifecycleOwner = getLifecycleOwner() } } diff --git a/app/src/main/java/org/oppia/android/app/testing/DragDropTestFragment.kt b/app/src/main/java/org/oppia/android/app/testing/DragDropTestFragment.kt index e14ca41b617..27a411c36e0 100644 --- a/app/src/main/java/org/oppia/android/app/testing/DragDropTestFragment.kt +++ b/app/src/main/java/org/oppia/android/app/testing/DragDropTestFragment.kt @@ -12,12 +12,7 @@ import org.oppia.android.app.recyclerview.OnDragEndedListener import org.oppia.android.app.recyclerview.OnItemDragListener import javax.inject.Inject -/** - * Fragment for displaying a [DragDropTestFragment]. - * Added to enable access to [BindableAdapter] - * which should be provided through dependency injection, - * this is replacing use case of [DragDropTestActivity] since adapter can not be injected to activities. - */ +/** Test-only fragment used for verifying ``BindableAdapter`` functionality. */ class DragDropTestFragment : InjectableFragment(), OnItemDragListener, OnDragEndedListener { companion object { diff --git a/app/src/main/res/layout/drag_drop_test_activity.xml b/app/src/main/res/layout/drag_drop_test_activity.xml index 976b3621f95..17928a10471 100644 --- a/app/src/main/res/layout/drag_drop_test_activity.xml +++ b/app/src/main/res/layout/drag_drop_test_activity.xml @@ -5,4 +5,4 @@ android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".app.testing.DragDropTestActivity"> - \ No newline at end of file + diff --git a/app/src/main/res/layout/drag_drop_test_fragment.xml b/app/src/main/res/layout/drag_drop_test_fragment.xml index 27b6d01e463..67b3a71e3e6 100644 --- a/app/src/main/res/layout/drag_drop_test_fragment.xml +++ b/app/src/main/res/layout/drag_drop_test_fragment.xml @@ -8,4 +8,4 @@ android:layout_height="wrap_content" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"> - \ No newline at end of file + From 89418ac23573049e09d5dbb5ea3c3527565060a7 Mon Sep 17 00:00:00 2001 From: kevingitonga Date: Wed, 27 Jul 2022 12:53:21 +0300 Subject: [PATCH 24/34] Fix nits and updates as advised during code review. --- .../app/player/state/DragDropSortInteractionView.kt | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/org/oppia/android/app/player/state/DragDropSortInteractionView.kt b/app/src/main/java/org/oppia/android/app/player/state/DragDropSortInteractionView.kt index 9bb18011f5d..635beab6c07 100644 --- a/app/src/main/java/org/oppia/android/app/player/state/DragDropSortInteractionView.kt +++ b/app/src/main/java/org/oppia/android/app/player/state/DragDropSortInteractionView.kt @@ -2,7 +2,6 @@ package org.oppia.android.app.player.state import android.content.Context import android.util.AttributeSet -import android.util.Log import android.view.LayoutInflater import androidx.core.view.isVisible import androidx.databinding.BindingAdapter @@ -69,9 +68,7 @@ class DragDropSortInteractionView @JvmOverloads constructor( viewComponent.inject(this) isAccessibilityEnabled = accessibilityService.isScreenReaderEnabled() - - Log.e("ATTACHING TO WIN", "ATTACHING TO WIN") - // maybeInitializeAdapter() + maybeInitializeAdapter() } private fun maybeInitializeAdapter() { @@ -97,14 +94,14 @@ class DragDropSortInteractionView @JvmOverloads constructor( // with setting the adapter data, so this needs to be done in an order-agnostic way. There should be a way to do // this more efficiently and cleanly than always relying on notifying of potential changes in the adapter when the // type is set (plus the type ought to be permanent). - // maybeInitializeAdapter() + maybeInitializeAdapter() this.isMultipleItemsInSamePositionAllowed = isAllowed } // TODO(#264): Clean up HTML parser such that it can be handled completely through a binding adapter, allowing // TextViews that require custom Oppia HTML parsing to be fully automatically bound through data-binding. fun setEntityId(entityId: String) { - // maybeInitializeAdapter() + maybeInitializeAdapter() this.entityId = entityId } @@ -190,7 +187,7 @@ fun setEntityId( dragDropSortInteractionView: DragDropSortInteractionView, entityId: String ) { - // dragDropSortInteractionView.setEntityId(entityId) + dragDropSortInteractionView.setEntityId(entityId) } /** Sets the [SelectionItemInputType] for a specific [SelectionInteractionView] via data-binding. */ @@ -199,5 +196,5 @@ fun setAllowMultipleItemsInSamePosition( dragDropSortInteractionView: DragDropSortInteractionView, isAllowed: Boolean ) { - // dragDropSortInteractionView.allowMultipleItemsInSamePosition(isAllowed) + dragDropSortInteractionView.allowMultipleItemsInSamePosition(isAllowed) } From 7b5e7fe0213680446cdd5af6f0b359bbe7c63871 Mon Sep 17 00:00:00 2001 From: kevingitonga Date: Thu, 28 Jul 2022 16:57:14 +0300 Subject: [PATCH 25/34] Updates issues as advised on code review, fix some of the failing tests. --- .../AdministratorControlsFragmentPresenter.kt | 4 ++-- .../ProfileAndDeviceIdFragmentPresenter.kt | 5 +++-- .../DeveloperOptionsFragmentPresenter.kt | 1 - .../MarkChaptersCompletedFragmentPresenter.kt | 5 +++-- .../app/fragment/FragmentComponentImpl.kt | 2 +- .../home/promotedlist/PromotedStoryListView.kt | 4 +--- .../state/StatePlayerRecyclerViewAssembler.kt | 10 +++++----- .../profile/ProfileChooserFragmentPresenter.kt | 5 +++-- .../ProfileProgressFragmentPresenter.kt | 4 ++-- .../android/app/recyclerview/BindableAdapter.kt | 6 +++++- .../profile/ProfileListFragmentPresenter.kt | 4 ++-- .../android/app/story/StoryFragmentPresenter.kt | 4 ++-- .../BindableAdapterTestFragmentPresenter.kt | 10 +++++----- .../app/testing/DragDropTestFragmentPresenter.kt | 4 ++-- .../app/recyclerview/BindableAdapterTest.kt | 16 ++++++++-------- .../app/testing/DragDropTestActivityTest.kt | 13 ++++++++----- 16 files changed, 52 insertions(+), 45 deletions(-) diff --git a/app/src/main/java/org/oppia/android/app/administratorcontrols/AdministratorControlsFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/administratorcontrols/AdministratorControlsFragmentPresenter.kt index 6fc33e4c2b0..7012c75a6bd 100644 --- a/app/src/main/java/org/oppia/android/app/administratorcontrols/AdministratorControlsFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/administratorcontrols/AdministratorControlsFragmentPresenter.kt @@ -32,7 +32,7 @@ import javax.inject.Inject class AdministratorControlsFragmentPresenter @Inject constructor( private val activity: AppCompatActivity, private val fragment: Fragment, - private val multiTypeBuilder: BindableAdapter.MultiTypeBuilder.Factory + private val multiTypeBuilderFactory: BindableAdapter.MultiTypeBuilder.Factory ) { private lateinit var binding: AdministratorControlsFragmentBinding private lateinit var linearLayoutManager: LinearLayoutManager @@ -78,7 +78,7 @@ class AdministratorControlsFragmentPresenter @Inject constructor( /** Returns the recycler view adapter for the controls panel in administrator controls fragment. */ private fun createRecyclerViewAdapter(isMultipane: Boolean): BindableAdapter { - return multiTypeBuilder + return multiTypeBuilderFactory .create { viewModel -> viewModel.isMultipane.set(isMultipane) when (viewModel) { diff --git a/app/src/main/java/org/oppia/android/app/administratorcontrols/learneranalytics/ProfileAndDeviceIdFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/administratorcontrols/learneranalytics/ProfileAndDeviceIdFragmentPresenter.kt index ca1b5cb1006..e75218bb232 100644 --- a/app/src/main/java/org/oppia/android/app/administratorcontrols/learneranalytics/ProfileAndDeviceIdFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/administratorcontrols/learneranalytics/ProfileAndDeviceIdFragmentPresenter.kt @@ -17,7 +17,7 @@ import javax.inject.Inject class ProfileAndDeviceIdFragmentPresenter @Inject constructor( private val fragment: Fragment, private val profileListViewModelFactory: ProfileListViewModel.Factory, - private val adapterFactory: BindableAdapter.MultiTypeBuilder.Factory + private val multiTypeBuilderFactory: BindableAdapter.MultiTypeBuilder.Factory ) { private lateinit var binding: ProfileAndDeviceIdFragmentBinding @@ -40,7 +40,8 @@ class ProfileAndDeviceIdFragmentPresenter @Inject constructor( } private fun createRecyclerViewAdapter(): BindableAdapter { - return adapterFactory.create { viewModel -> + return multiTypeBuilderFactory.create { viewModel -> when (viewModel) { is DeviceIdItemViewModel -> ProfileListItemViewType.DEVICE_ID is ProfileLearnerIdItemViewModel -> ProfileListItemViewType.LEARNER_ID diff --git a/app/src/main/java/org/oppia/android/app/devoptions/DeveloperOptionsFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/devoptions/DeveloperOptionsFragmentPresenter.kt index 2f53aef47e3..1b280beb5ef 100644 --- a/app/src/main/java/org/oppia/android/app/devoptions/DeveloperOptionsFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/devoptions/DeveloperOptionsFragmentPresenter.kt @@ -26,7 +26,6 @@ class DeveloperOptionsFragmentPresenter @Inject constructor( private val activity: AppCompatActivity, private val fragment: Fragment, private val multiTypeBuilderFactory: BindableAdapter.MultiTypeBuilder.Factory - ) { private lateinit var binding: DeveloperOptionsFragmentBinding diff --git a/app/src/main/java/org/oppia/android/app/devoptions/markchapterscompleted/MarkChaptersCompletedFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/devoptions/markchapterscompleted/MarkChaptersCompletedFragmentPresenter.kt index 63c448d7a88..fee0646f45f 100644 --- a/app/src/main/java/org/oppia/android/app/devoptions/markchapterscompleted/MarkChaptersCompletedFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/devoptions/markchapterscompleted/MarkChaptersCompletedFragmentPresenter.kt @@ -24,7 +24,7 @@ class MarkChaptersCompletedFragmentPresenter @Inject constructor( private val fragment: Fragment, private val viewModelProvider: ViewModelProvider, private val modifyLessonProgressController: ModifyLessonProgressController, - private val adapterFactory: BindableAdapter.MultiTypeBuilder.Factory + private val multiTypeBuilderFactory: BindableAdapter.MultiTypeBuilder.Factory ) : ChapterSelector { private lateinit var binding: MarkChaptersCompletedFragmentBinding private lateinit var linearLayoutManager: LinearLayoutManager @@ -95,7 +95,8 @@ class MarkChaptersCompletedFragmentPresenter @Inject constructor( } private fun createRecyclerViewAdapter(): BindableAdapter { - return adapterFactory.create { viewModel -> + return multiTypeBuilderFactory.create { viewModel -> when (viewModel) { is StorySummaryViewModel -> ViewType.VIEW_TYPE_STORY is ChapterSummaryViewModel -> ViewType.VIEW_TYPE_CHAPTER diff --git a/app/src/main/java/org/oppia/android/app/fragment/FragmentComponentImpl.kt b/app/src/main/java/org/oppia/android/app/fragment/FragmentComponentImpl.kt index 71688e3a4db..8c2496770b3 100644 --- a/app/src/main/java/org/oppia/android/app/fragment/FragmentComponentImpl.kt +++ b/app/src/main/java/org/oppia/android/app/fragment/FragmentComponentImpl.kt @@ -111,6 +111,7 @@ interface FragmentComponentImpl : FragmentComponent, ViewComponentBuilderInjecto fun inject(conceptCardFragment: ConceptCardFragment) fun inject(developerOptionsFragment: DeveloperOptionsFragment) fun inject(downloadsTabFragment: DownloadsTabFragment) + fun inject(dragDropTestFragment: DragDropTestFragment) fun inject(exitProfileDialogFragment: ExitProfileDialogFragment) fun inject(explorationFragment: ExplorationFragment) fun inject(explorationManagerFragment: ExplorationManagerFragment) @@ -168,5 +169,4 @@ interface FragmentComponentImpl : FragmentComponent, ViewComponentBuilderInjecto fun inject(walkthroughFinalFragment: WalkthroughFinalFragment) fun inject(walkthroughTopicListFragment: WalkthroughTopicListFragment) fun inject(walkthroughWelcomeFragment: WalkthroughWelcomeFragment) - fun inject(dragDropTestFragment: DragDropTestFragment) } diff --git a/app/src/main/java/org/oppia/android/app/home/promotedlist/PromotedStoryListView.kt b/app/src/main/java/org/oppia/android/app/home/promotedlist/PromotedStoryListView.kt index 58fcd0bbb03..74467bff9a1 100644 --- a/app/src/main/java/org/oppia/android/app/home/promotedlist/PromotedStoryListView.kt +++ b/app/src/main/java/org/oppia/android/app/home/promotedlist/PromotedStoryListView.kt @@ -39,8 +39,7 @@ class PromotedStoryListView @JvmOverloads constructor( override fun onAttachedToWindow() { super.onAttachedToWindow() - val viewComponentFactory = - FragmentManager.findFragment(this) as ViewComponentFactory + val viewComponentFactory = FragmentManager.findFragment(this) as ViewComponentFactory val viewComponent = viewComponentFactory.createViewComponent(this) as ViewComponentImpl viewComponent.inject(this) @@ -69,7 +68,6 @@ class PromotedStoryListView @JvmOverloads constructor( * @param newDataList the new list of stories to present */ fun setPromotedStoryList(newDataList: List?) { - if (newDataList == null) { oppiaLogger.w(PROMOTED_STORY_LIST_VIEW_TAG, "Failed to resolve new topics list data") } else { diff --git a/app/src/main/java/org/oppia/android/app/player/state/StatePlayerRecyclerViewAssembler.kt b/app/src/main/java/org/oppia/android/app/player/state/StatePlayerRecyclerViewAssembler.kt index 16ae481159a..31a222956ad 100644 --- a/app/src/main/java/org/oppia/android/app/player/state/StatePlayerRecyclerViewAssembler.kt +++ b/app/src/main/java/org/oppia/android/app/player/state/StatePlayerRecyclerViewAssembler.kt @@ -888,12 +888,12 @@ class StatePlayerRecyclerViewAssembler private constructor( private val backgroundCoroutineDispatcher: CoroutineDispatcher, private val resourceHandler: AppLanguageResourceHandler, private val translationController: TranslationController, - private val adapterBuilderFactory: BindableAdapter.MultiTypeBuilder.Factory, - private val singleAdapterFactory: BindableAdapter.SingleTypeBuilder.Factory + private val multiTypeBuilderFactory: BindableAdapter.MultiTypeBuilder.Factory, + private val singleTypeBuilderFactory: BindableAdapter.SingleTypeBuilder.Factory ) { private val adapterBuilder: BindableAdapter.MultiTypeBuilder = adapterBuilderFactory.create { it.viewType } + StateItemViewModel.ViewType> = multiTypeBuilderFactory.create { it.viewType } /** * Tracks features individually enabled for the assembler. No features are enabled by default. @@ -1118,7 +1118,7 @@ class StatePlayerRecyclerViewAssembler private constructor( gcsEntityId: String, supportsConceptCards: Boolean ): BindableAdapter { - return singleAdapterFactory.create() + return singleTypeBuilderFactory.create() .registerViewBinder( inflateView = { parent -> SubmittedAnswerListItemBinding.inflate( @@ -1139,7 +1139,7 @@ class StatePlayerRecyclerViewAssembler private constructor( gcsEntityId: String, supportsConceptCards: Boolean ): BindableAdapter { - return singleAdapterFactory.create() + return singleTypeBuilderFactory.create() .registerViewBinder( inflateView = { parent -> SubmittedHtmlAnswerItemBinding.inflate( diff --git a/app/src/main/java/org/oppia/android/app/profile/ProfileChooserFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/profile/ProfileChooserFragmentPresenter.kt index b3a86046b56..1dfcbff875a 100644 --- a/app/src/main/java/org/oppia/android/app/profile/ProfileChooserFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/profile/ProfileChooserFragmentPresenter.kt @@ -66,7 +66,7 @@ class ProfileChooserFragmentPresenter @Inject constructor( private val viewModelProvider: ViewModelProvider, private val profileManagementController: ProfileManagementController, private val oppiaLogger: OppiaLogger, - private val adapterBuilder: BindableAdapter.MultiTypeBuilder.Factory + private val multiTypeBuilderFactory: BindableAdapter.MultiTypeBuilder.Factory ) { private lateinit var binding: ProfileChooserFragmentBinding val hasProfileEverBeenAddedValue = ObservableField(true) @@ -149,7 +149,8 @@ class ProfileChooserFragmentPresenter @Inject constructor( } private fun createRecyclerViewAdapter(): BindableAdapter { - return adapterBuilder.create( + return multiTypeBuilderFactory.create( ProfileChooserUiModel::getModelTypeCase ) .registerViewDataBinderWithSameModelType( diff --git a/app/src/main/java/org/oppia/android/app/profileprogress/ProfileProgressFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/profileprogress/ProfileProgressFragmentPresenter.kt index e39fdbed8a7..3d01e34a33d 100644 --- a/app/src/main/java/org/oppia/android/app/profileprogress/ProfileProgressFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/profileprogress/ProfileProgressFragmentPresenter.kt @@ -22,7 +22,7 @@ class ProfileProgressFragmentPresenter @Inject constructor( private val activity: AppCompatActivity, private val fragment: Fragment, private val viewModel: ProfileProgressViewModel, - private val multiTypeAdapterBuilder: BindableAdapter.MultiTypeBuilder.Factory + private val multiTypeBuilderFactory: BindableAdapter.MultiTypeBuilder.Factory ) { fun handleCreateView( @@ -69,7 +69,7 @@ class ProfileProgressFragmentPresenter @Inject constructor( } private fun createRecyclerViewAdapter(): BindableAdapter { - return multiTypeAdapterBuilder.create { viewModel -> + return multiTypeBuilderFactory.create { viewModel -> when (viewModel) { is ProfileProgressHeaderViewModel -> ViewType.VIEW_TYPE_HEADER is RecentlyPlayedStorySummaryViewModel -> ViewType.VIEW_TYPE_RECENTLY_PLAYED_STORY diff --git a/app/src/main/java/org/oppia/android/app/recyclerview/BindableAdapter.kt b/app/src/main/java/org/oppia/android/app/recyclerview/BindableAdapter.kt index 0d6724fbb38..69e49acd807 100644 --- a/app/src/main/java/org/oppia/android/app/recyclerview/BindableAdapter.kt +++ b/app/src/main/java/org/oppia/android/app/recyclerview/BindableAdapter.kt @@ -336,7 +336,11 @@ class BindableAdapter internal constructor( setViewModel(binding, transformViewModel(data)) // Attaching lifecycleOwner before view model initialization can sometimes cause a // NullPointerException because data might not be attached to the views yet. - binding.lifecycleOwner = getLifecycleOwner() + // Skip attaching Lifecycle when StateFragment is created as this is causing NPE, yet to find fix. + val currentFragment = getLifecycleOwner()?.javaClass?.simpleName.toString() + if (currentFragment != "StateFragment" || currentFragment != "QuestionPlayerFragment") { + binding.lifecycleOwner = getLifecycleOwner() + } } } } diff --git a/app/src/main/java/org/oppia/android/app/settings/profile/ProfileListFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/settings/profile/ProfileListFragmentPresenter.kt index 064be19a51b..41846084168 100644 --- a/app/src/main/java/org/oppia/android/app/settings/profile/ProfileListFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/settings/profile/ProfileListFragmentPresenter.kt @@ -20,7 +20,7 @@ class ProfileListFragmentPresenter @Inject constructor( private val activity: AppCompatActivity, private val fragment: Fragment, private val viewModelProvider: ViewModelProvider, - private val singleTypeAdapterBuilder: BindableAdapter.SingleTypeBuilder.Factory + private val singleTypeBuilderFactory: BindableAdapter.SingleTypeBuilder.Factory ) { private var isMultipane = false @@ -53,7 +53,7 @@ class ProfileListFragmentPresenter @Inject constructor( } private fun createRecyclerViewAdapter(): BindableAdapter { - return singleTypeAdapterBuilder.create() + return singleTypeBuilderFactory.create() .registerViewDataBinderWithSameModelType( inflateDataBinding = ProfileListProfileViewBinding::inflate, setViewModel = ::bindProfileView diff --git a/app/src/main/java/org/oppia/android/app/story/StoryFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/story/StoryFragmentPresenter.kt index bd76ea0a933..7680716ea05 100644 --- a/app/src/main/java/org/oppia/android/app/story/StoryFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/story/StoryFragmentPresenter.kt @@ -50,7 +50,7 @@ class StoryFragmentPresenter @Inject constructor( @DefaultResourceBucketName private val resourceBucketName: String, @TopicHtmlParserEntityType private val entityType: String, private val resourceHandler: AppLanguageResourceHandler, - private val multiTypeAdapterFactory: BindableAdapter.MultiTypeBuilder.Factory + private val multiTypeBuilderFactory: BindableAdapter.MultiTypeBuilder.Factory ) { private val routeToExplorationListener = activity as RouteToExplorationListener private val routeToResumeLessonListener = activity as RouteToResumeLessonListener @@ -136,7 +136,7 @@ class StoryFragmentPresenter @Inject constructor( } private fun createRecyclerViewAdapter(): BindableAdapter { - return multiTypeAdapterFactory.create { viewModel -> + return multiTypeBuilderFactory.create { viewModel -> when (viewModel) { is StoryHeaderViewModel -> ViewType.VIEW_TYPE_HEADER is StoryChapterSummaryViewModel -> ViewType.VIEW_TYPE_CHAPTER diff --git a/app/src/main/java/org/oppia/android/app/testing/BindableAdapterTestFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/testing/BindableAdapterTestFragmentPresenter.kt index 25961d8ba67..94b8c23f506 100644 --- a/app/src/main/java/org/oppia/android/app/testing/BindableAdapterTestFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/testing/BindableAdapterTestFragmentPresenter.kt @@ -12,8 +12,8 @@ import javax.inject.Inject /** The test-only fragment presenter corresponding to [BindableAdapterTestFragment]. */ class BindableAdapterTestFragmentPresenter @Inject constructor( private val fragment: Fragment, - private val singleTypeBuilder: BindableAdapter.SingleTypeBuilder.Factory, - private val multiTypeBuilder: BindableAdapter.MultiTypeBuilder.Factory, + private val singleTypeBuilderFactory: BindableAdapter.SingleTypeBuilder.Factory, + private val multiTypeBuilderFactory: BindableAdapter.MultiTypeBuilder.Factory, private val testBindableAdapterFactory: BindableAdapterFactory, @VisibleForTesting val viewModel: BindableAdapterTestViewModel ) { @@ -24,7 +24,7 @@ class BindableAdapterTestFragmentPresenter @Inject constructor( /* attachToRoot= */ false ) binding.testRecyclerView.apply { - adapter = testBindableAdapterFactory.create(singleTypeBuilder, multiTypeBuilder) + adapter = testBindableAdapterFactory.create(singleTypeBuilderFactory, multiTypeBuilderFactory) } binding.let { it.viewModel = viewModel @@ -36,8 +36,8 @@ class BindableAdapterTestFragmentPresenter @Inject constructor( /** Factory for creating new [BindableAdapter]s for the current fragment. */ interface BindableAdapterFactory { fun create( - singleTypeBuilder: BindableAdapter.SingleTypeBuilder.Factory, - multiTypeBuilder: BindableAdapter.MultiTypeBuilder.Factory + singleTypeBuilderFactory: BindableAdapter.SingleTypeBuilder.Factory, + multiTypeBuilderFactory: BindableAdapter.MultiTypeBuilder.Factory ): BindableAdapter } } diff --git a/app/src/main/java/org/oppia/android/app/testing/DragDropTestFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/testing/DragDropTestFragmentPresenter.kt index 3e6da0e1e55..56c6dc94410 100644 --- a/app/src/main/java/org/oppia/android/app/testing/DragDropTestFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/testing/DragDropTestFragmentPresenter.kt @@ -14,7 +14,7 @@ import javax.inject.Inject /** The presenter for [DragDropTestFragment]. */ class DragDropTestFragmentPresenter @Inject constructor( private val activity: AppCompatActivity, - private val singleTypeBuilder: BindableAdapter.SingleTypeBuilder.Factory + private val singleTypeBuilderFactory: BindableAdapter.SingleTypeBuilder.Factory ) { private var dataList = mutableListOf("Item 1", "Item 2", "Item 3", "Item 4") @@ -39,7 +39,7 @@ class DragDropTestFragmentPresenter @Inject constructor( } private fun createBindableAdapter(): BindableAdapter { - return singleTypeBuilder.create() + return singleTypeBuilderFactory.create() .registerViewBinder( inflateView = this::inflateTextViewForStringWithoutDataBinding, bindView = this::bindTextViewForStringWithoutDataBinding diff --git a/app/src/sharedTest/java/org/oppia/android/app/recyclerview/BindableAdapterTest.kt b/app/src/sharedTest/java/org/oppia/android/app/recyclerview/BindableAdapterTest.kt index 2c85a003543..891e46e1eba 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/recyclerview/BindableAdapterTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/recyclerview/BindableAdapterTest.kt @@ -434,9 +434,9 @@ class BindableAdapterTest { } private fun createSingleViewTypeNoDataBindingBindableAdapter( - singleTypeBuilder: SingleTypeBuilder.Factory + singleTypeBuilderFactory: SingleTypeBuilder.Factory ): BindableAdapter { - return singleTypeBuilder.create() + return singleTypeBuilderFactory.create() .registerViewBinder( inflateView = this::inflateTextViewForStringWithoutDataBinding, bindView = this::bindTextViewForStringWithoutDataBinding @@ -473,10 +473,10 @@ class BindableAdapterTest { } private fun createMultiViewTypeNoDataBindingBindableAdapter( - multiTypeBuilder: MultiTypeBuilder.Factory + multiTypeBuilderFactory: MultiTypeBuilder.Factory ): BindableAdapter { - return multiTypeBuilder.create(ViewModelType.Companion::deriveTypeFrom) + return multiTypeBuilderFactory.create(ViewModelType.Companion::deriveTypeFrom) .registerViewBinder( viewType = ViewModelType.STRING, inflateView = this::inflateTextViewForStringWithoutDataBinding, @@ -491,9 +491,9 @@ class BindableAdapterTest { } private fun createMultiViewTypeWithDataBindingBindableAdapter( - multiTypeBuilder: MultiTypeBuilder.Factory + multiTypeBuilderFactory: MultiTypeBuilder.Factory ): BindableAdapter { - return multiTypeBuilder.create(ViewModelType.Companion::deriveTypeFrom) + return multiTypeBuilderFactory.create(ViewModelType.Companion::deriveTypeFrom) .registerViewDataBinderWithSameModelType( viewType = ViewModelType.STRING, inflateDataBinding = TestTextViewForStringWithDataBindingBinding::inflate, @@ -598,9 +598,9 @@ class BindableAdapterTest { return object : BindableAdapterTestFragmentPresenter.BindableAdapterFactory { override fun create( singleTypeBuilder: SingleTypeBuilder.Factory, - multiTypeBuilder: MultiTypeBuilder.Factory + multiTypeBuilderFactory: MultiTypeBuilder.Factory ): BindableAdapter { - return createFunction(singleTypeBuilder, multiTypeBuilder) + return createFunction(singleTypeBuilder, multiTypeBuilderFactory) } } } diff --git a/app/src/sharedTest/java/org/oppia/android/app/testing/DragDropTestActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/testing/DragDropTestActivityTest.kt index 0d3c55261f1..4b15373d574 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/testing/DragDropTestActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/testing/DragDropTestActivityTest.kt @@ -189,15 +189,18 @@ class DragDropTestActivityTest { } private fun attachDragDropToActivity(activity: DragDropTestActivity) { - val recyclerView: RecyclerView = activity.findViewById(R.id.drag_drop_recycler_view) - val itemTouchHelper = ItemTouchHelper(createDragCallback(activity)) + val dragDragTestFragment: DragDropTestFragment = activity.supportFragmentManager + .findFragmentById(R.id.drag_drop_test_fragment_placeholder) as DragDropTestFragment + val recyclerView: RecyclerView? = + dragDragTestFragment.view?.findViewById(R.id.drag_drop_recycler_view) + val itemTouchHelper = ItemTouchHelper(createDragCallback(fragment = dragDragTestFragment)) itemTouchHelper.attachToRecyclerView(recyclerView) } - private fun createDragCallback(activity: DragDropTestActivity): ItemTouchHelper.Callback { + private fun createDragCallback(fragment: DragDropTestFragment): ItemTouchHelper.Callback { return DragAndDropItemFacilitator( - activity as OnItemDragListener, - activity as OnDragEndedListener + fragment as OnItemDragListener, + fragment as OnDragEndedListener ) } From d026b128702b67a397ff474de1347678f63d843e Mon Sep 17 00:00:00 2001 From: kevingitonga Date: Mon, 1 Aug 2022 10:35:43 +0300 Subject: [PATCH 26/34] Fix failing tests. --- .../java/org/oppia/android/app/recyclerview/BindableAdapter.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/oppia/android/app/recyclerview/BindableAdapter.kt b/app/src/main/java/org/oppia/android/app/recyclerview/BindableAdapter.kt index 69e49acd807..8ecf8e0409f 100644 --- a/app/src/main/java/org/oppia/android/app/recyclerview/BindableAdapter.kt +++ b/app/src/main/java/org/oppia/android/app/recyclerview/BindableAdapter.kt @@ -338,7 +338,7 @@ class BindableAdapter internal constructor( // NullPointerException because data might not be attached to the views yet. // Skip attaching Lifecycle when StateFragment is created as this is causing NPE, yet to find fix. val currentFragment = getLifecycleOwner()?.javaClass?.simpleName.toString() - if (currentFragment != "StateFragment" || currentFragment != "QuestionPlayerFragment") { + if (currentFragment != "QuestionPlayerFragment" && currentFragment != "StateFragment") { binding.lifecycleOwner = getLifecycleOwner() } } From b784db5277f62054e3842873708311357f6a4495 Mon Sep 17 00:00:00 2001 From: kevingitonga Date: Mon, 1 Aug 2022 17:08:55 +0300 Subject: [PATCH 27/34] Fix more failing tests by reverting DragDropSortInteractionView.kt and SelectionInteractionView.kt to initial state. --- .../state/DragDropSortInteractionView.kt | 34 +++---------------- .../player/state/SelectionInteractionView.kt | 20 +---------- 2 files changed, 6 insertions(+), 48 deletions(-) diff --git a/app/src/main/java/org/oppia/android/app/player/state/DragDropSortInteractionView.kt b/app/src/main/java/org/oppia/android/app/player/state/DragDropSortInteractionView.kt index 635beab6c07..289bcc69301 100644 --- a/app/src/main/java/org/oppia/android/app/player/state/DragDropSortInteractionView.kt +++ b/app/src/main/java/org/oppia/android/app/player/state/DragDropSortInteractionView.kt @@ -62,31 +62,12 @@ class DragDropSortInteractionView @JvmOverloads constructor( override fun onAttachedToWindow() { super.onAttachedToWindow() - val viewComponentFactory = - FragmentManager.findFragment(this) as ViewComponentFactory + + val viewComponentFactory = FragmentManager.findFragment(this) as ViewComponentFactory val viewComponent = viewComponentFactory.createViewComponent(this) as ViewComponentImpl viewComponent.inject(this) isAccessibilityEnabled = accessibilityService.isScreenReaderEnabled() - maybeInitializeAdapter() - } - - private fun maybeInitializeAdapter() { - if (::htmlParserFactory.isInitialized && - ::accessibilityService.isInitialized && - ::entityType.isInitialized && - ::resourceBucketName.isInitialized && - ::viewBindingShim.isInitialized && - ::singleTypeBuilderFactory.isInitialized - ) { - bindDataToAdapter() - } - } - - private fun bindDataToAdapter() { - if (adapter == null) { - adapter = createAdapter() - } } fun allowMultipleItemsInSamePosition(isAllowed: Boolean) { @@ -94,14 +75,13 @@ class DragDropSortInteractionView @JvmOverloads constructor( // with setting the adapter data, so this needs to be done in an order-agnostic way. There should be a way to do // this more efficiently and cleanly than always relying on notifying of potential changes in the adapter when the // type is set (plus the type ought to be permanent). - maybeInitializeAdapter() this.isMultipleItemsInSamePositionAllowed = isAllowed + adapter = createAdapter() } // TODO(#264): Clean up HTML parser such that it can be handled completely through a binding adapter, allowing // TextViews that require custom Oppia HTML parsing to be fully automatically bound through data-binding. fun setEntityId(entityId: String) { - maybeInitializeAdapter() this.entityId = entityId } @@ -186,15 +166,11 @@ class DragDropSortInteractionView @JvmOverloads constructor( fun setEntityId( dragDropSortInteractionView: DragDropSortInteractionView, entityId: String -) { - dragDropSortInteractionView.setEntityId(entityId) -} +) = dragDropSortInteractionView.setEntityId(entityId) /** Sets the [SelectionItemInputType] for a specific [SelectionInteractionView] via data-binding. */ @BindingAdapter("allowMultipleItemsInSamePosition") fun setAllowMultipleItemsInSamePosition( dragDropSortInteractionView: DragDropSortInteractionView, isAllowed: Boolean -) { - dragDropSortInteractionView.allowMultipleItemsInSamePosition(isAllowed) -} +) = dragDropSortInteractionView.allowMultipleItemsInSamePosition(isAllowed) diff --git a/app/src/main/java/org/oppia/android/app/player/state/SelectionInteractionView.kt b/app/src/main/java/org/oppia/android/app/player/state/SelectionInteractionView.kt index b3707a68bac..b7dcf363372 100644 --- a/app/src/main/java/org/oppia/android/app/player/state/SelectionInteractionView.kt +++ b/app/src/main/java/org/oppia/android/app/player/state/SelectionInteractionView.kt @@ -57,22 +57,6 @@ class SelectionInteractionView @JvmOverloads constructor( val viewComponentFactory = FragmentManager.findFragment(this) as ViewComponentFactory val viewComponent = viewComponentFactory.createViewComponent(this) as ViewComponentImpl viewComponent.inject(this) - maybeInitializeAdapter() - } - - private fun maybeInitializeAdapter() { - if (::bindingInterface.isInitialized && - ::htmlParserFactory.isInitialized && - ::entityType.isInitialized && - ::resourceBucketName.isInitialized && - ::singleTypeBuilderFactory.isInitialized - ) { - bindDataToAdapter() - } - } - - private fun bindDataToAdapter() { - adapter = createAdapter() } fun setAllOptionsItemInputType(selectionItemInputType: SelectionItemInputType) { @@ -81,14 +65,13 @@ class SelectionInteractionView @JvmOverloads constructor( // this more efficiently and cleanly than always relying on notifying of potential changes in the adapter when the // type is set (plus the type ought to be permanent). this.selectionItemInputType = selectionItemInputType - maybeInitializeAdapter() + adapter = createAdapter() } // TODO(#264): Clean up HTML parser such that it can be handled completely through a binding adapter, allowing // TextViews that require custom Oppia HTML parsing to be fully automatically bound through data-binding. fun setEntityId(entityId: String) { this.entityId = entityId - maybeInitializeAdapter() } /** @@ -98,7 +81,6 @@ class SelectionInteractionView @JvmOverloads constructor( */ fun setWrittenTranslationContext(writtenTranslationContext: WrittenTranslationContext) { this.writtenTranslationContext = writtenTranslationContext - maybeInitializeAdapter() } private fun createAdapter(): BindableAdapter { From 86cdf12f63cd90d5b9794a91088830592650a694 Mon Sep 17 00:00:00 2001 From: kevingitonga Date: Mon, 1 Aug 2022 19:55:34 +0300 Subject: [PATCH 28/34] Update multiple field namings for consistency as advised by code reviewer. --- .../MarkTopicsCompletedFragmentPresenter.kt | 4 ++-- .../vieweventlogs/ViewEventLogsFragmentPresenter.kt | 4 ++-- .../HintsAndSolutionDialogFragmentPresenter.kt | 4 ++-- .../org/oppia/android/app/home/HomeFragmentPresenter.kt | 4 ++-- .../app/home/promotedlist/ComingSoonTopicsListView.kt | 6 +++--- .../app/topic/lessons/TopicLessonsFragmentPresenter.kt | 4 ++-- .../app/topic/practice/TopicPracticeFragmentPresenter.kt | 4 ++-- .../app/topic/revision/TopicRevisionFragmentPresenter.kt | 4 ++-- .../topiclist/WalkthroughTopicListFragmentPresenter.kt | 4 ++-- 9 files changed, 19 insertions(+), 19 deletions(-) diff --git a/app/src/main/java/org/oppia/android/app/devoptions/marktopicscompleted/MarkTopicsCompletedFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/devoptions/marktopicscompleted/MarkTopicsCompletedFragmentPresenter.kt index fb4127c3450..5d9139a786c 100644 --- a/app/src/main/java/org/oppia/android/app/devoptions/marktopicscompleted/MarkTopicsCompletedFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/devoptions/marktopicscompleted/MarkTopicsCompletedFragmentPresenter.kt @@ -22,7 +22,7 @@ class MarkTopicsCompletedFragmentPresenter @Inject constructor( private val fragment: Fragment, private val viewModelProvider: ViewModelProvider, private val modifyLessonProgressController: ModifyLessonProgressController, - private val singleTypeAdapterFactory: BindableAdapter.SingleTypeBuilder.Factory + private val singleTypeBuilderFactory: BindableAdapter.SingleTypeBuilder.Factory ) : TopicSelector { private lateinit var binding: MarkTopicsCompletedFragmentBinding private lateinit var linearLayoutManager: LinearLayoutManager @@ -90,7 +90,7 @@ class MarkTopicsCompletedFragmentPresenter @Inject constructor( } private fun createRecyclerViewAdapter(): BindableAdapter { - return singleTypeAdapterFactory.create() + return singleTypeBuilderFactory.create() .registerViewDataBinderWithSameModelType( inflateDataBinding = MarkTopicsCompletedTopicViewBinding::inflate, setViewModel = this::bindTopicSummaryView diff --git a/app/src/main/java/org/oppia/android/app/devoptions/vieweventlogs/ViewEventLogsFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/devoptions/vieweventlogs/ViewEventLogsFragmentPresenter.kt index 6f0222142e7..9fe0b2849f8 100644 --- a/app/src/main/java/org/oppia/android/app/devoptions/vieweventlogs/ViewEventLogsFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/devoptions/vieweventlogs/ViewEventLogsFragmentPresenter.kt @@ -19,7 +19,7 @@ class ViewEventLogsFragmentPresenter @Inject constructor( private val activity: AppCompatActivity, private val fragment: Fragment, private val viewModelProvider: ViewModelProvider, - private val singleTypeAdapterFactory: BindableAdapter.SingleTypeBuilder.Factory + private val singleTypeBuilderFactory: BindableAdapter.SingleTypeBuilder.Factory ) { private lateinit var binding: ViewEventLogsFragmentBinding @@ -57,7 +57,7 @@ class ViewEventLogsFragmentPresenter @Inject constructor( } private fun createRecyclerViewAdapter(): BindableAdapter { - return singleTypeAdapterFactory.create() + return singleTypeBuilderFactory.create() .registerViewDataBinderWithSameModelType( inflateDataBinding = ViewEventLogsEventLogItemViewBinding::inflate, setViewModel = ViewEventLogsEventLogItemViewBinding::setViewModel diff --git a/app/src/main/java/org/oppia/android/app/hintsandsolution/HintsAndSolutionDialogFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/hintsandsolution/HintsAndSolutionDialogFragmentPresenter.kt index f8aba56f4b6..617172f046d 100644 --- a/app/src/main/java/org/oppia/android/app/hintsandsolution/HintsAndSolutionDialogFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/hintsandsolution/HintsAndSolutionDialogFragmentPresenter.kt @@ -37,7 +37,7 @@ class HintsAndSolutionDialogFragmentPresenter @Inject constructor( @DefaultResourceBucketName private val resourceBucketName: String, @ExplorationHtmlParserEntityType private val entityType: String, private val resourceHandler: AppLanguageResourceHandler, - private val multiTypeAdapterFactory: BindableAdapter.MultiTypeBuilder.Factory + private val multiTypeBuilderFactory: BindableAdapter.MultiTypeBuilder.Factory ) { private var currentExpandedHintListIndex: Int? = null @@ -168,7 +168,7 @@ class HintsAndSolutionDialogFragmentPresenter @Inject constructor( } private fun createRecyclerViewAdapter(): BindableAdapter { - return multiTypeAdapterFactory.create { viewModel -> + return multiTypeBuilderFactory.create { viewModel -> when (viewModel) { is HintsViewModel -> ViewType.VIEW_TYPE_HINT_ITEM is SolutionViewModel -> ViewType.VIEW_TYPE_SOLUTION_ITEM diff --git a/app/src/main/java/org/oppia/android/app/home/HomeFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/home/HomeFragmentPresenter.kt index cbf14a75538..75c84f2448c 100644 --- a/app/src/main/java/org/oppia/android/app/home/HomeFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/home/HomeFragmentPresenter.kt @@ -42,7 +42,7 @@ class HomeFragmentPresenter @Inject constructor( @StoryHtmlParserEntityType private val storyEntityType: String, private val resourceHandler: AppLanguageResourceHandler, private val dateTimeUtil: DateTimeUtil, - private val multiTypeAdapterFactory: BindableAdapter.MultiTypeBuilder.Factory + private val multiTypeBuilderFactory: BindableAdapter.MultiTypeBuilder.Factory ) { private val routeToTopicListener = activity as RouteToTopicListener private lateinit var binding: HomeFragmentBinding @@ -94,7 +94,7 @@ class HomeFragmentPresenter @Inject constructor( } private fun createRecyclerViewAdapter(): BindableAdapter { - return multiTypeAdapterFactory.create { viewModel -> + return multiTypeBuilderFactory.create { viewModel -> when (viewModel) { is WelcomeViewModel -> ViewType.WELCOME_MESSAGE is PromotedStoryListViewModel -> ViewType.PROMOTED_STORY_LIST diff --git a/app/src/main/java/org/oppia/android/app/home/promotedlist/ComingSoonTopicsListView.kt b/app/src/main/java/org/oppia/android/app/home/promotedlist/ComingSoonTopicsListView.kt index 2e76b8d1b2e..9a2e13f4808 100644 --- a/app/src/main/java/org/oppia/android/app/home/promotedlist/ComingSoonTopicsListView.kt +++ b/app/src/main/java/org/oppia/android/app/home/promotedlist/ComingSoonTopicsListView.kt @@ -33,7 +33,7 @@ class ComingSoonTopicsListView @JvmOverloads constructor( lateinit var oppiaLogger: OppiaLogger @Inject - lateinit var singleTypeAdapterFactory: BindableAdapter.SingleTypeBuilder.Factory + lateinit var singleTypeBuilderFactory: BindableAdapter.SingleTypeBuilder.Factory private lateinit var comingSoonDataList: List @@ -57,7 +57,7 @@ class ComingSoonTopicsListView @JvmOverloads constructor( if (::bindingInterface.isInitialized && ::bindingInterface.isInitialized && ::oppiaLogger.isInitialized && - ::singleTypeAdapterFactory.isInitialized && + ::singleTypeBuilderFactory.isInitialized && ::comingSoonDataList.isInitialized ) { bindDataToAdapter() @@ -96,7 +96,7 @@ class ComingSoonTopicsListView @JvmOverloads constructor( } private fun createAdapter(): BindableAdapter { - return singleTypeAdapterFactory.create() + return singleTypeBuilderFactory.create() .registerViewBinder( inflateView = { parent -> bindingInterface.provideComingSoonTopicViewInflatedView( diff --git a/app/src/main/java/org/oppia/android/app/topic/lessons/TopicLessonsFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/topic/lessons/TopicLessonsFragmentPresenter.kt index e148a5aba53..cc4eb31742c 100644 --- a/app/src/main/java/org/oppia/android/app/topic/lessons/TopicLessonsFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/topic/lessons/TopicLessonsFragmentPresenter.kt @@ -35,7 +35,7 @@ class TopicLessonsFragmentPresenter @Inject constructor( private val oppiaLogger: OppiaLogger, private val explorationDataController: ExplorationDataController, private val explorationCheckpointController: ExplorationCheckpointController, - private val multiTypeAdapterFactory: BindableAdapter.MultiTypeBuilder.Factory, + private val multiTypeBuilderFactory: BindableAdapter.MultiTypeBuilder.Factory, private val singleTypeBuilderFactory: BindableAdapter.SingleTypeBuilder.Factory ) { @@ -106,7 +106,7 @@ class TopicLessonsFragmentPresenter @Inject constructor( } private fun createRecyclerViewAdapter(): BindableAdapter { - return multiTypeAdapterFactory.create { viewModel -> + return multiTypeBuilderFactory.create { viewModel -> when (viewModel) { is StorySummaryViewModel -> ViewType.VIEW_TYPE_STORY_ITEM is TopicLessonsTitleViewModel -> ViewType.VIEW_TYPE_TITLE_TEXT diff --git a/app/src/main/java/org/oppia/android/app/topic/practice/TopicPracticeFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/topic/practice/TopicPracticeFragmentPresenter.kt index ee596399a90..9612776e141 100644 --- a/app/src/main/java/org/oppia/android/app/topic/practice/TopicPracticeFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/topic/practice/TopicPracticeFragmentPresenter.kt @@ -28,7 +28,7 @@ class TopicPracticeFragmentPresenter @Inject constructor( private val fragment: Fragment, private val oppiaLogger: OppiaLogger, private val viewModelProvider: ViewModelProvider, - private val multiTypeAdapterFactory: BindableAdapter.MultiTypeBuilder.Factory + private val multiTypeBuilderFactory: BindableAdapter.MultiTypeBuilder.Factory ) : SubtopicSelector { private lateinit var binding: TopicPracticeFragmentBinding private lateinit var linearLayoutManager: LinearLayoutManager @@ -74,7 +74,7 @@ class TopicPracticeFragmentPresenter @Inject constructor( } private fun createRecyclerViewAdapter(): BindableAdapter { - return multiTypeAdapterFactory.create { viewModel -> + return multiTypeBuilderFactory.create { viewModel -> when (viewModel) { is TopicPracticeHeaderViewModel -> ViewType.VIEW_TYPE_HEADER is TopicPracticeSubtopicViewModel -> ViewType.VIEW_TYPE_SKILL diff --git a/app/src/main/java/org/oppia/android/app/topic/revision/TopicRevisionFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/topic/revision/TopicRevisionFragmentPresenter.kt index c795ec3e52d..64d177fda26 100755 --- a/app/src/main/java/org/oppia/android/app/topic/revision/TopicRevisionFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/topic/revision/TopicRevisionFragmentPresenter.kt @@ -23,7 +23,7 @@ class TopicRevisionFragmentPresenter @Inject constructor( activity: AppCompatActivity, private val fragment: Fragment, private val viewModelProvider: ViewModelProvider, - private val singleTypeAdapterFactory: BindableAdapter.SingleTypeBuilder.Factory + private val singleTypeBuilderFactory: BindableAdapter.SingleTypeBuilder.Factory ) : RevisionSubtopicSelector { private lateinit var binding: TopicRevisionFragmentBinding private var internalProfileId: Int = -1 @@ -71,7 +71,7 @@ class TopicRevisionFragmentPresenter @Inject constructor( } private fun createRecyclerViewAdapter(): BindableAdapter { - return singleTypeAdapterFactory.create() + return singleTypeBuilderFactory.create() .registerViewDataBinderWithSameModelType( inflateDataBinding = TopicRevisionSummaryViewBinding::inflate, setViewModel = TopicRevisionSummaryViewBinding::setViewModel diff --git a/app/src/main/java/org/oppia/android/app/walkthrough/topiclist/WalkthroughTopicListFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/walkthrough/topiclist/WalkthroughTopicListFragmentPresenter.kt index ba1a76d76a5..9e29832f78b 100644 --- a/app/src/main/java/org/oppia/android/app/walkthrough/topiclist/WalkthroughTopicListFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/walkthrough/topiclist/WalkthroughTopicListFragmentPresenter.kt @@ -27,7 +27,7 @@ class WalkthroughTopicListFragmentPresenter @Inject constructor( val activity: AppCompatActivity, private val fragment: Fragment, private val viewModelProvider: ViewModelProvider, - private val multiTypeAdapterFactory: BindableAdapter.MultiTypeBuilder.Factory + private val multiTypeBuilderFactory: BindableAdapter.MultiTypeBuilder.Factory ) { private lateinit var binding: WalkthroughTopicListFragmentBinding private val routeToNextPage = activity as WalkthroughFragmentChangeListener @@ -74,7 +74,7 @@ class WalkthroughTopicListFragmentPresenter @Inject constructor( } private fun createRecyclerViewAdapter(): BindableAdapter { - return multiTypeAdapterFactory.create { viewModel -> + return multiTypeBuilderFactory.create { viewModel -> when (viewModel) { is WalkthroughTopicHeaderViewModel -> ViewType.VIEW_TYPE_HEADER is WalkthroughTopicSummaryViewModel -> ViewType.VIEW_TYPE_TOPIC From 6c9a6c4a5b92ef2e430b6d2122ca166d913c8034 Mon Sep 17 00:00:00 2001 From: kevingitonga Date: Thu, 11 Aug 2022 14:52:43 +0300 Subject: [PATCH 29/34] Fix failing tests and revert from previous fix, which could not be used in alpha. --- .../android/app/player/state/DragDropSortInteractionView.kt | 2 +- .../android/app/player/state/SelectionInteractionView.kt | 3 +-- .../org/oppia/android/app/recyclerview/BindableAdapter.kt | 6 +----- 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/org/oppia/android/app/player/state/DragDropSortInteractionView.kt b/app/src/main/java/org/oppia/android/app/player/state/DragDropSortInteractionView.kt index 289bcc69301..e36e7832f85 100644 --- a/app/src/main/java/org/oppia/android/app/player/state/DragDropSortInteractionView.kt +++ b/app/src/main/java/org/oppia/android/app/player/state/DragDropSortInteractionView.kt @@ -53,7 +53,6 @@ class DragDropSortInteractionView @JvmOverloads constructor( @Inject lateinit var viewBindingShim: ViewBindingShim - @Inject lateinit var singleTypeBuilderFactory: BindableAdapter.SingleTypeBuilder.Factory private lateinit var entityId: String @@ -76,6 +75,7 @@ class DragDropSortInteractionView @JvmOverloads constructor( // this more efficiently and cleanly than always relying on notifying of potential changes in the adapter when the // type is set (plus the type ought to be permanent). this.isMultipleItemsInSamePositionAllowed = isAllowed + singleTypeBuilderFactory = BindableAdapter.SingleTypeBuilder.Factory(Fragment()) adapter = createAdapter() } diff --git a/app/src/main/java/org/oppia/android/app/player/state/SelectionInteractionView.kt b/app/src/main/java/org/oppia/android/app/player/state/SelectionInteractionView.kt index b7dcf363372..fc079393837 100644 --- a/app/src/main/java/org/oppia/android/app/player/state/SelectionInteractionView.kt +++ b/app/src/main/java/org/oppia/android/app/player/state/SelectionInteractionView.kt @@ -45,7 +45,6 @@ class SelectionInteractionView @JvmOverloads constructor( @Inject lateinit var bindingInterface: ViewBindingShim - @Inject lateinit var singleTypeBuilderFactory: BindableAdapter.SingleTypeBuilder.Factory private lateinit var entityId: String @@ -53,7 +52,6 @@ class SelectionInteractionView @JvmOverloads constructor( override fun onAttachedToWindow() { super.onAttachedToWindow() - val viewComponentFactory = FragmentManager.findFragment(this) as ViewComponentFactory val viewComponent = viewComponentFactory.createViewComponent(this) as ViewComponentImpl viewComponent.inject(this) @@ -65,6 +63,7 @@ class SelectionInteractionView @JvmOverloads constructor( // this more efficiently and cleanly than always relying on notifying of potential changes in the adapter when the // type is set (plus the type ought to be permanent). this.selectionItemInputType = selectionItemInputType + singleTypeBuilderFactory = BindableAdapter.SingleTypeBuilder.Factory(Fragment()) adapter = createAdapter() } diff --git a/app/src/main/java/org/oppia/android/app/recyclerview/BindableAdapter.kt b/app/src/main/java/org/oppia/android/app/recyclerview/BindableAdapter.kt index 8ecf8e0409f..0d6724fbb38 100644 --- a/app/src/main/java/org/oppia/android/app/recyclerview/BindableAdapter.kt +++ b/app/src/main/java/org/oppia/android/app/recyclerview/BindableAdapter.kt @@ -336,11 +336,7 @@ class BindableAdapter internal constructor( setViewModel(binding, transformViewModel(data)) // Attaching lifecycleOwner before view model initialization can sometimes cause a // NullPointerException because data might not be attached to the views yet. - // Skip attaching Lifecycle when StateFragment is created as this is causing NPE, yet to find fix. - val currentFragment = getLifecycleOwner()?.javaClass?.simpleName.toString() - if (currentFragment != "QuestionPlayerFragment" && currentFragment != "StateFragment") { - binding.lifecycleOwner = getLifecycleOwner() - } + binding.lifecycleOwner = getLifecycleOwner() } } } From 6432c23bd05789809107f20c469f70fb94746ed9 Mon Sep 17 00:00:00 2001 From: Ben Henning Date: Wed, 21 Sep 2022 17:20:04 -0700 Subject: [PATCH 30/34] Fix out-of-order binding for selection, drop/drop. --- .../state/DragDropSortInteractionView.kt | 129 ++++++++---------- .../player/state/SelectionInteractionView.kt | 81 ++++++----- .../app/recyclerview/BindableAdapter.kt | 22 +-- .../res/layout/drag_drop_interaction_item.xml | 2 +- .../res/layout/selection_interaction_item.xml | 2 +- 5 files changed, 105 insertions(+), 131 deletions(-) diff --git a/app/src/main/java/org/oppia/android/app/player/state/DragDropSortInteractionView.kt b/app/src/main/java/org/oppia/android/app/player/state/DragDropSortInteractionView.kt index e36e7832f85..0ab4d264da6 100644 --- a/app/src/main/java/org/oppia/android/app/player/state/DragDropSortInteractionView.kt +++ b/app/src/main/java/org/oppia/android/app/player/state/DragDropSortInteractionView.kt @@ -4,7 +4,6 @@ import android.content.Context import android.util.AttributeSet import android.view.LayoutInflater import androidx.core.view.isVisible -import androidx.databinding.BindingAdapter import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager import androidx.recyclerview.widget.ItemTouchHelper @@ -22,6 +21,7 @@ import org.oppia.android.util.gcsresource.DefaultResourceBucketName import org.oppia.android.util.parser.html.ExplorationHtmlParserEntityType import org.oppia.android.util.parser.html.HtmlParser import javax.inject.Inject +import kotlin.properties.Delegates /** * A custom [RecyclerView] for displaying a list of items that can be re-ordered using @@ -32,30 +32,18 @@ class DragDropSortInteractionView @JvmOverloads constructor( attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : RecyclerView(context, attrs, defStyleAttr) { - // For disabling grouping of items by default. - private var isMultipleItemsInSamePositionAllowed: Boolean = false - private var isAccessibilityEnabled: Boolean = false + @field:[Inject ExplorationHtmlParserEntityType] lateinit var entityType: String + @field:[Inject DefaultResourceBucketName] lateinit var resourceBucketName: String - @Inject - lateinit var htmlParserFactory: HtmlParser.Factory - - @Inject - lateinit var accessibilityService: AccessibilityService - - @Inject - @field:ExplorationHtmlParserEntityType - lateinit var entityType: String - - @Inject - @field:DefaultResourceBucketName - lateinit var resourceBucketName: String - - @Inject - lateinit var viewBindingShim: ViewBindingShim - - lateinit var singleTypeBuilderFactory: BindableAdapter.SingleTypeBuilder.Factory + @Inject lateinit var htmlParserFactory: HtmlParser.Factory + @Inject lateinit var accessibilityService: AccessibilityService + @Inject lateinit var viewBindingShim: ViewBindingShim + @Inject lateinit var singleTypeBuilderFactory: BindableAdapter.SingleTypeBuilder.Factory + private var multipleItemsInSamePositionInitialized = false + private var isMultipleItemsInSamePositionAllowed by Delegates.notNull() private lateinit var entityId: String + private lateinit var dataList: List private lateinit var onDragEnd: OnDragEndedListener private lateinit var onItemDrag: OnItemDragListener @@ -65,24 +53,59 @@ class DragDropSortInteractionView @JvmOverloads constructor( val viewComponentFactory = FragmentManager.findFragment(this) as ViewComponentFactory val viewComponent = viewComponentFactory.createViewComponent(this) as ViewComponentImpl viewComponent.inject(this) - - isAccessibilityEnabled = accessibilityService.isScreenReaderEnabled() + maybeInitializeAdapter() } - fun allowMultipleItemsInSamePosition(isAllowed: Boolean) { - // TODO(#299): Find a cleaner way to initialize the item input type. Using data-binding results in a race condition - // with setting the adapter data, so this needs to be done in an order-agnostic way. There should be a way to do - // this more efficiently and cleanly than always relying on notifying of potential changes in the adapter when the - // type is set (plus the type ought to be permanent). + fun setAllowMultipleItemsInSamePosition(isAllowed: Boolean) { this.isMultipleItemsInSamePositionAllowed = isAllowed - singleTypeBuilderFactory = BindableAdapter.SingleTypeBuilder.Factory(Fragment()) - adapter = createAdapter() + multipleItemsInSamePositionInitialized = true + maybeInitializeAdapter() } - // TODO(#264): Clean up HTML parser such that it can be handled completely through a binding adapter, allowing - // TextViews that require custom Oppia HTML parsing to be fully automatically bound through data-binding. + // TODO(#264): Clean up HTML parser such that it can be handled completely through a binding + // adapter, allowing TextViews that require custom Oppia HTML parsing to be fully automatically + // bound through data-binding. fun setEntityId(entityId: String) { this.entityId = entityId + maybeInitializeAdapter() + } + + /** + * Sets the view's RecyclerView [DragDropInteractionContentViewModel] data list. + * + * Note that this needs to be used instead of the generic RecyclerView 'data' binding adapter + * since this one takes into account initialization order with other binding properties. + */ + fun setDraggableData(dataList: List) { + this.dataList = dataList + maybeInitializeAdapter() + } + + fun setOnDragEnded(onDragEnd: OnDragEndedListener) { + this.onDragEnd = onDragEnd + maybeAttachItemTouchHelper() + } + + fun setOnItemDrag(onItemDrag: OnItemDragListener) { + this.onItemDrag = onItemDrag + maybeAttachItemTouchHelper() + } + + private fun maybeInitializeAdapter() { + if (::singleTypeBuilderFactory.isInitialized && + multipleItemsInSamePositionInitialized && + ::entityId.isInitialized && + ::dataList.isInitialized + ) { + adapter = createAdapter().also { it.setData(dataList) } + } + } + + private fun maybeAttachItemTouchHelper() { + if (::onDragEnd.isInitialized && ::onItemDrag.isInitialized) { + val itemTouchHelper = ItemTouchHelper(DragAndDropItemFacilitator(onItemDrag, onDragEnd)) + itemTouchHelper.attachToRecyclerView(this) + } } private fun createAdapter(): BindableAdapter { @@ -105,7 +128,7 @@ class DragDropSortInteractionView @JvmOverloads constructor( viewBindingShim.getDragDropInteractionItemsBindingUnlinkItems().isVisible = viewModel.htmlContent.contentIdsList.size > 1 viewBindingShim.getDragDropInteractionItemsBindingAccessibleContainer().isVisible = - isAccessibilityEnabled + accessibilityService.isScreenReaderEnabled() viewBindingShim.setDragDropInteractionItemsBindingViewModel(viewModel) } ) @@ -135,42 +158,4 @@ class DragDropSortInteractionView @JvmOverloads constructor( ) .build() } - - fun setOnDragEnded(onDragEnd: OnDragEndedListener) { - this.onDragEnd = onDragEnd - checkIfSettingIsPossible() - } - - fun setOnItemDrag(onItemDrag: OnItemDragListener) { - this.onItemDrag = onItemDrag - checkIfSettingIsPossible() - } - - private fun checkIfSettingIsPossible() { - if (::onDragEnd.isInitialized && ::onItemDrag.isInitialized) { - performAttachment() - } - } - - private fun performAttachment() { - val dragCallback: ItemTouchHelper.Callback = - DragAndDropItemFacilitator(onItemDrag, onDragEnd) - - val itemTouchHelper = ItemTouchHelper(dragCallback) - itemTouchHelper.attachToRecyclerView(this) - } } - -/** Sets the exploration ID for a specific [DragDropSortInteractionView] via data-binding. */ -@BindingAdapter("entityId") -fun setEntityId( - dragDropSortInteractionView: DragDropSortInteractionView, - entityId: String -) = dragDropSortInteractionView.setEntityId(entityId) - -/** Sets the [SelectionItemInputType] for a specific [SelectionInteractionView] via data-binding. */ -@BindingAdapter("allowMultipleItemsInSamePosition") -fun setAllowMultipleItemsInSamePosition( - dragDropSortInteractionView: DragDropSortInteractionView, - isAllowed: Boolean -) = dragDropSortInteractionView.allowMultipleItemsInSamePosition(isAllowed) diff --git a/app/src/main/java/org/oppia/android/app/player/state/SelectionInteractionView.kt b/app/src/main/java/org/oppia/android/app/player/state/SelectionInteractionView.kt index fc079393837..1265ed41808 100644 --- a/app/src/main/java/org/oppia/android/app/player/state/SelectionInteractionView.kt +++ b/app/src/main/java/org/oppia/android/app/player/state/SelectionInteractionView.kt @@ -3,7 +3,7 @@ package org.oppia.android.app.player.state import android.content.Context import android.util.AttributeSet import android.view.LayoutInflater -import androidx.databinding.BindingAdapter +import androidx.databinding.ObservableList import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager import androidx.recyclerview.widget.RecyclerView @@ -20,57 +20,45 @@ import org.oppia.android.util.parser.html.HtmlParser import javax.inject.Inject /** - * A custom [RecyclerView] for displaying a variable list of items that may be selected by a user as part of the item - * selection or multiple choice interactions. + * A custom [RecyclerView] for displaying a variable list of items that may be selected by a user as + * part of the item selection or multiple choice interactions. */ class SelectionInteractionView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : RecyclerView(context, attrs, defStyleAttr) { - // Default to checkboxes to ensure that something can render even if it may not be correct. - private var selectionItemInputType: SelectionItemInputType = SelectionItemInputType.CHECKBOXES + @field:[Inject ExplorationHtmlParserEntityType] lateinit var entityType: String + @field:[Inject DefaultResourceBucketName] lateinit var resourceBucketName: String - @Inject - lateinit var htmlParserFactory: HtmlParser.Factory - - @Inject - @field:ExplorationHtmlParserEntityType - lateinit var entityType: String - - @Inject - @field:DefaultResourceBucketName - lateinit var resourceBucketName: String - - @Inject - lateinit var bindingInterface: ViewBindingShim - - lateinit var singleTypeBuilderFactory: BindableAdapter.SingleTypeBuilder.Factory + @Inject lateinit var htmlParserFactory: HtmlParser.Factory + @Inject lateinit var bindingInterface: ViewBindingShim + @Inject lateinit var singleTypeBuilderFactory: BindableAdapter.SingleTypeBuilder.Factory + private lateinit var selectionItemInputType: SelectionItemInputType private lateinit var entityId: String private lateinit var writtenTranslationContext: WrittenTranslationContext + private lateinit var dataList: ObservableList override fun onAttachedToWindow() { super.onAttachedToWindow() val viewComponentFactory = FragmentManager.findFragment(this) as ViewComponentFactory val viewComponent = viewComponentFactory.createViewComponent(this) as ViewComponentImpl viewComponent.inject(this) + maybeInitializeAdapter() } fun setAllOptionsItemInputType(selectionItemInputType: SelectionItemInputType) { - // TODO(#299): Find a cleaner way to initialize the item input type. Using data-binding results in a race condition - // with setting the adapter data, so this needs to be done in an order-agnostic way. There should be a way to do - // this more efficiently and cleanly than always relying on notifying of potential changes in the adapter when the - // type is set (plus the type ought to be permanent). this.selectionItemInputType = selectionItemInputType - singleTypeBuilderFactory = BindableAdapter.SingleTypeBuilder.Factory(Fragment()) - adapter = createAdapter() + maybeInitializeAdapter() } - // TODO(#264): Clean up HTML parser such that it can be handled completely through a binding adapter, allowing - // TextViews that require custom Oppia HTML parsing to be fully automatically bound through data-binding. + // TODO(#264): Clean up HTML parser such that it can be handled completely through a binding + // adapter, allowing TextViews that require custom Oppia HTML parsing to be fully automatically + // bound through data-binding. fun setEntityId(entityId: String) { this.entityId = entityId + maybeInitializeAdapter() } /** @@ -80,6 +68,29 @@ class SelectionInteractionView @JvmOverloads constructor( */ fun setWrittenTranslationContext(writtenTranslationContext: WrittenTranslationContext) { this.writtenTranslationContext = writtenTranslationContext + maybeInitializeAdapter() + } + + /** + * Sets the view's RecyclerView [SelectionInteractionContentViewModel] data list. + * + * Note that this needs to be used instead of the generic RecyclerView 'data' binding adapter + * since this one takes into account initialization order with other binding properties. + */ + fun setSelectionData(dataList: ObservableList) { + this.dataList = dataList + maybeInitializeAdapter() + } + + private fun maybeInitializeAdapter() { + if (::singleTypeBuilderFactory.isInitialized && + ::selectionItemInputType.isInitialized && + ::entityId.isInitialized && + ::writtenTranslationContext.isInitialized && + ::dataList.isInitialized + ) { + adapter = createAdapter().also { it.setData(dataList) } + } } private fun createAdapter(): BindableAdapter { @@ -133,17 +144,3 @@ class SelectionInteractionView @JvmOverloads constructor( } } } - -/** Sets the exploration ID for a specific [SelectionInteractionView] via data-binding. */ -@BindingAdapter("entityId") -fun setEntityId( - selectionInteractionView: SelectionInteractionView, - entityId: String -) = selectionInteractionView.setEntityId(entityId) - -/** Sets the translation context for a specific [SelectionInteractionView] via data-binding. */ -@BindingAdapter("writtenTranslationContext") -fun setWrittenTranslationContext( - selectionInteractionView: SelectionInteractionView, - writtenTranslationContext: WrittenTranslationContext -) = selectionInteractionView.setWrittenTranslationContext(writtenTranslationContext) diff --git a/app/src/main/java/org/oppia/android/app/recyclerview/BindableAdapter.kt b/app/src/main/java/org/oppia/android/app/recyclerview/BindableAdapter.kt index 0d6724fbb38..e584e47104b 100644 --- a/app/src/main/java/org/oppia/android/app/recyclerview/BindableAdapter.kt +++ b/app/src/main/java/org/oppia/android/app/recyclerview/BindableAdapter.kt @@ -226,13 +226,10 @@ class BindableAdapter internal constructor( } /** Fragment injectable factory to create new [SingleTypeBuilder]. */ - class Factory @Inject constructor( - val fragment: Fragment - ) { - /**Returns a new [SingleTypeBuilder] for the specified Data class type.*/ - inline fun create(): SingleTypeBuilder { - return SingleTypeBuilder(T::class, fragment) - } + class Factory @Inject constructor(val fragment: Fragment) { + /** Returns a new [SingleTypeBuilder] for the specified Data class type. */ + inline fun create(): SingleTypeBuilder = + SingleTypeBuilder(T::class, fragment) } } @@ -362,16 +359,11 @@ class BindableAdapter internal constructor( } /** Fragment injectable factory to create new [MultiTypeBuilder]. */ - class Factory @Inject constructor( - val fragment: Fragment - ) { - - /*** Returns a new [MultiTypeBuilder] for the specified data class type.*/ + class Factory @Inject constructor(val fragment: Fragment) { + /** Returns a new [MultiTypeBuilder] for the specified data class type. */ inline fun > create( noinline computeViewType: ComputeViewType - ): MultiTypeBuilder { - return MultiTypeBuilder(T::class, computeViewType, fragment) - } + ): MultiTypeBuilder = MultiTypeBuilder(T::class, computeViewType, fragment) } } } diff --git a/app/src/main/res/layout/drag_drop_interaction_item.xml b/app/src/main/res/layout/drag_drop_interaction_item.xml index fe40177f834..53a7074bcac 100644 --- a/app/src/main/res/layout/drag_drop_interaction_item.xml +++ b/app/src/main/res/layout/drag_drop_interaction_item.xml @@ -64,7 +64,7 @@ app:allowMultipleItemsInSamePosition="@{viewModel.getGroupingStatus()}" app:entityId="@{viewModel.entityId}" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" - app:list="@{viewModel.choiceItems}" + app:draggableData="@{viewModel.choiceItems}" app:onDragEnded="@{(adapter) -> viewModel.onDragEnded(adapter)}" app:onItemDrag="@{(indexFrom, indexTo, adapter) -> viewModel.onItemDragged(indexFrom, indexTo, adapter)}" /> diff --git a/app/src/main/res/layout/selection_interaction_item.xml b/app/src/main/res/layout/selection_interaction_item.xml index b7f12ec8e59..e5670567d1f 100644 --- a/app/src/main/res/layout/selection_interaction_item.xml +++ b/app/src/main/res/layout/selection_interaction_item.xml @@ -54,7 +54,7 @@ android:layout_height="wrap_content" android:divider="@android:color/transparent" app:allOptionsItemInputType="@{viewModel.getSelectionItemInputType()}" - app:data="@{viewModel.choiceItems}" + app:selectionData="@{viewModel.choiceItems}" app:entityId="@{viewModel.entityId}" app:writtenTranslationContext="@{viewModel.writtenTranslationContext}" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" /> From 5a8d7cfd98395ffea9dd2a7d4a4273763660421b Mon Sep 17 00:00:00 2001 From: Ben Henning Date: Thu, 22 Sep 2022 15:55:13 -0700 Subject: [PATCH 31/34] Post-merge fixes. Mainly, this fixing previous code as well as image region selection (which whose breakage could only be detected via manually playing the image region selection and by running the interaction's corresponding test suite on Espresso, both of which caught different sets of issues). The PR includes some cleanup work as well. --- .../state/DragDropSortInteractionView.kt | 17 +- .../ImageRegionSelectionInteractionView.kt | 139 +++++++--------- .../app/recyclerview/BindableAdapter.kt | 31 ++-- .../ImageRegionSelectionTestActivity.kt | 13 ++ .../ImageRegionSelectionTestFragment.kt | 10 +- ...ageRegionSelectionTestFragmentPresenter.kt | 18 ++- .../app/utility/ClickableAreasImage.kt | 151 ++++++++---------- .../image_region_selection_test_fragment.xml | 35 ++-- ...ImageRegionSelectionInteractionViewTest.kt | 91 +++++------ .../util/parser/image/TestGlideImageLoader.kt | 28 +++- 10 files changed, 267 insertions(+), 266 deletions(-) diff --git a/app/src/main/java/org/oppia/android/app/player/state/DragDropSortInteractionView.kt b/app/src/main/java/org/oppia/android/app/player/state/DragDropSortInteractionView.kt index 0ab4d264da6..706c3f57013 100644 --- a/app/src/main/java/org/oppia/android/app/player/state/DragDropSortInteractionView.kt +++ b/app/src/main/java/org/oppia/android/app/player/state/DragDropSortInteractionView.kt @@ -21,11 +21,10 @@ import org.oppia.android.util.gcsresource.DefaultResourceBucketName import org.oppia.android.util.parser.html.ExplorationHtmlParserEntityType import org.oppia.android.util.parser.html.HtmlParser import javax.inject.Inject -import kotlin.properties.Delegates /** * A custom [RecyclerView] for displaying a list of items that can be re-ordered using - * [DragItemTouchHelperCallback]. + * [DragAndDropItemFacilitator]. */ class DragDropSortInteractionView @JvmOverloads constructor( context: Context, @@ -41,7 +40,7 @@ class DragDropSortInteractionView @JvmOverloads constructor( @Inject lateinit var singleTypeBuilderFactory: BindableAdapter.SingleTypeBuilder.Factory private var multipleItemsInSamePositionInitialized = false - private var isMultipleItemsInSamePositionAllowed by Delegates.notNull() + private var isMultipleItemsInSamePositionAllowed: Boolean? = null private lateinit var entityId: String private lateinit var dataList: List private lateinit var onDragEnd: OnDragEndedListener @@ -92,12 +91,14 @@ class DragDropSortInteractionView @JvmOverloads constructor( } private fun maybeInitializeAdapter() { + val itemsInSamePositionAllowed = isMultipleItemsInSamePositionAllowed if (::singleTypeBuilderFactory.isInitialized && multipleItemsInSamePositionInitialized && ::entityId.isInitialized && - ::dataList.isInitialized + ::dataList.isInitialized && + itemsInSamePositionAllowed != null ) { - adapter = createAdapter().also { it.setData(dataList) } + adapter = createAdapter(itemsInSamePositionAllowed).also { it.setData(dataList) } } } @@ -108,7 +109,9 @@ class DragDropSortInteractionView @JvmOverloads constructor( } } - private fun createAdapter(): BindableAdapter { + private fun createAdapter( + itemsInSamePositionAllowed: Boolean + ): BindableAdapter { return singleTypeBuilderFactory.create() .registerViewBinder( inflateView = { parent -> @@ -124,7 +127,7 @@ class DragDropSortInteractionView @JvmOverloads constructor( createNestedAdapter() adapter?.let { viewBindingShim.setDragDropInteractionItemsBindingAdapter(it) } viewBindingShim.getDragDropInteractionItemsBindingGroupItem().isVisible = - isMultipleItemsInSamePositionAllowed + itemsInSamePositionAllowed viewBindingShim.getDragDropInteractionItemsBindingUnlinkItems().isVisible = viewModel.htmlContent.contentIdsList.size > 1 viewBindingShim.getDragDropInteractionItemsBindingAccessibleContainer().isVisible = diff --git a/app/src/main/java/org/oppia/android/app/player/state/ImageRegionSelectionInteractionView.kt b/app/src/main/java/org/oppia/android/app/player/state/ImageRegionSelectionInteractionView.kt index 24d845c32c3..d94b853f1ef 100644 --- a/app/src/main/java/org/oppia/android/app/player/state/ImageRegionSelectionInteractionView.kt +++ b/app/src/main/java/org/oppia/android/app/player/state/ImageRegionSelectionInteractionView.kt @@ -25,7 +25,7 @@ import org.oppia.android.util.parser.image.ImageViewTarget import javax.inject.Inject /** - * A custom [AppCompatImageView] with a list of [LabeledRegion] to work with + * A custom [AppCompatImageView] with a list of [ImageWithRegions.LabeledRegion]s to work with * [ClickableAreasImage]. * * In order to correctly work with this interaction make sure you've called attached an listener @@ -36,67 +36,34 @@ class ImageRegionSelectionInteractionView @JvmOverloads constructor( attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : AppCompatImageView(context, attrs, defStyleAttr) { + @field:[Inject ExplorationHtmlParserEntityType] lateinit var entityType: String + @field:[Inject DefaultResourceBucketName] lateinit var resourceBucketName: String + @field:[Inject ImageDownloadUrlTemplate] lateinit var imageDownloadUrlTemplate: String + @field:[Inject DefaultGcsPrefix] lateinit var gcsPrefix: String - private var isAccessibilityEnabled: Boolean = false - private lateinit var imageUrl: String - private var clickableAreas: List = emptyList() - private lateinit var listener: OnClickableAreaClickedListener - - @Inject - lateinit var accessibilityService: AccessibilityService - - @Inject - lateinit var imageLoader: ImageLoader - - @Inject - @field:ExplorationHtmlParserEntityType - lateinit var entityType: String - - @Inject - @field:DefaultResourceBucketName - lateinit var resourceBucketName: String - - @Inject - @field:ImageDownloadUrlTemplate - lateinit var imageDownloadUrlTemplate: String - - @Inject - @field:DefaultGcsPrefix - lateinit var gcsPrefix: String - - @Inject - lateinit var bindingInterface: ViewBindingShim - - @Inject - lateinit var machineLocale: OppiaLocale.MachineLocale + @Inject lateinit var bindingInterface: ViewBindingShim + @Inject lateinit var machineLocale: OppiaLocale.MachineLocale + @Inject lateinit var accessibilityService: AccessibilityService + @Inject lateinit var imageLoader: ImageLoader private lateinit var entityId: String private lateinit var overlayView: FrameLayout private lateinit var onRegionClicked: OnClickableAreaClickedListener + private lateinit var imageUrl: String + private lateinit var clickableAreas: List /** - * Sets the URL for the image & initiates loading it. This is intended to be called via data-binding. + * Sets the URL for the image & initiates loading it. This is intended to be called via + * data-binding. */ fun setImageUrl(imageUrl: String) { this.imageUrl = imageUrl - loadImage() - } - - /** Initiates the asynchronous loading process for the interaction's image region. */ - private fun loadImage() { - val imageName = machineLocale.run { - imageDownloadUrlTemplate.formatForMachines(entityType, entityId, imageUrl) - } - val imageUrl = "$gcsPrefix/$resourceBucketName/$imageName" - if (machineLocale.run { imageUrl.endsWithIgnoreCase("svg") }) { - imageLoader.loadBlockSvg(imageUrl, ImageViewTarget(this)) - } else { - imageLoader.loadBitmap(imageUrl, ImageViewTarget(this)) - } + maybeInitializeClickableAreas() } fun setEntityId(entityId: String) { this.entityId = entityId + maybeInitializeClickableAreas() } fun setClickableAreas(clickableAreas: List) { @@ -112,26 +79,7 @@ class ImageRegionSelectionInteractionView @JvmOverloads constructor( } } } - } - - /** Binds [OnClickableAreaClickedListener] with the view inorder to get callback from [ClickableAreasImage]. */ - fun setListener(onClickableAreaClickedListener: OnClickableAreaClickedListener) { - this.listener = onClickableAreaClickedListener - val area = ClickableAreasImage( - this, - this.parent as FrameLayout, - listener, - bindingInterface - ) - area.addRegionViews() - } - - fun getClickableAreas(): List { - return clickableAreas - } - - fun isAccessibilityEnabled(): Boolean { - return isAccessibilityEnabled + maybeInitializeClickableAreas() } override fun onAttachedToWindow() { @@ -140,34 +88,56 @@ class ImageRegionSelectionInteractionView @JvmOverloads constructor( val viewComponentFactory = FragmentManager.findFragment(this) as ViewComponentFactory val viewComponent = viewComponentFactory.createViewComponent(this) as ViewComponentImpl viewComponent.inject(this) - - isAccessibilityEnabled = accessibilityService.isScreenReaderEnabled() + maybeInitializeClickableAreas() } fun setOnRegionClicked(onRegionClicked: OnClickableAreaClickedListener) { this.onRegionClicked = onRegionClicked - checkIfSettingIsPossible() + maybeInitializeClickableAreas() } fun setOverlayView(overlayView: FrameLayout) { this.overlayView = overlayView - checkIfSettingIsPossible() + maybeInitializeClickableAreas() } - private fun checkIfSettingIsPossible() { - if (::onRegionClicked.isInitialized && ::overlayView.isInitialized) { - performAttachment() + private fun maybeInitializeClickableAreas() { + if (::accessibilityService.isInitialized && + ::clickableAreas.isInitialized && + ::entityId.isInitialized && + ::imageUrl.isInitialized && + ::onRegionClicked.isInitialized && + ::overlayView.isInitialized + ) { + loadImage() + + val areasImage = ClickableAreasImage( + this, + this.parent as FrameLayout, + onRegionClicked, + bindingInterface, + isAccessibilityEnabled = accessibilityService.isScreenReaderEnabled(), + clickableAreas + ) + areasImage.addRegionViews() + performAttachment(areasImage) } } - private fun performAttachment() { - val area = ClickableAreasImage( - this, - overlayView, - onRegionClicked, - bindingInterface - ) + /** Initiates the asynchronous loading process for the interaction's image region. */ + private fun loadImage() { + val imageName = machineLocale.run { + imageDownloadUrlTemplate.formatForMachines(entityType, entityId, imageUrl) + } + val imageUrl = "$gcsPrefix/$resourceBucketName/$imageName" + if (machineLocale.run { imageUrl.endsWithIgnoreCase("svg") }) { + imageLoader.loadBlockSvg(imageUrl, ImageViewTarget(this)) + } else { + imageLoader.loadBitmap(imageUrl, ImageViewTarget(this)) + } + } + private fun performAttachment(areasImage: ClickableAreasImage) { this.addOnLayoutChangeListener { _, left, @@ -179,8 +149,9 @@ class ImageRegionSelectionInteractionView @JvmOverloads constructor( oldRight, oldBottom -> // Update the regions, as the bounds have changed - if (left != oldLeft || top != oldTop || right != oldRight || bottom != oldBottom) - area.addRegionViews() + if (left != oldLeft || top != oldTop || right != oldRight || bottom != oldBottom) { + areasImage.addRegionViews() + } } } } diff --git a/app/src/main/java/org/oppia/android/app/recyclerview/BindableAdapter.kt b/app/src/main/java/org/oppia/android/app/recyclerview/BindableAdapter.kt index e584e47104b..018be6cfd91 100644 --- a/app/src/main/java/org/oppia/android/app/recyclerview/BindableAdapter.kt +++ b/app/src/main/java/org/oppia/android/app/recyclerview/BindableAdapter.kt @@ -108,25 +108,26 @@ class BindableAdapter internal constructor( private val lifecycleOwnerRef: WeakReference = WeakReference(fragment) /** - * Returns the [LifecycleOwner] bound to this adapter, or null if there isn't one. This method - * will throw if there was a lifecycle owner bound but is now expired. + * The [LifecycleOwner] bound to this adapter. + * + * Attempting to call this property will throw if the bound [LifecycleOwner] is expired (i.e. + * indicating that it's been cleaned up by the system and is no longer valid). */ - protected fun getLifecycleOwner(): LifecycleOwner? { - // Crash if the lifecycle owner has been cleaned up since it's not valid to use the adapter - // with an old lifecycle owner, and silently ignoring this may result in part of the layout - // not responding to events. - return lifecycleOwnerRef?.let { ref -> - checkNotNull(ref.get()) { + protected val lifecycleOwner: LifecycleOwner + get() { + // Crash if the lifecycle owner has been cleaned up since it's not valid to use the adapter + // with an old lifecycle owner, and silently ignoring this may result in part of the layout + // not responding to events. + return checkNotNull(lifecycleOwnerRef.get()) { "Attempted to inflate data binding with expired lifecycle owner" } } - } } /** * Constructs a new [BindableAdapter] that for a single view type. * - * Instances of [SingleTypeBuilder] should be instantiated using [newBuilder]. + * Instances of this class can be created by injecting [Factory] and calling [Factory.create]. */ class SingleTypeBuilder( private val dataClassType: KClass, @@ -206,9 +207,10 @@ class BindableAdapter internal constructor( object : BindableViewHolder(binding.root) { override fun bind(data: T) { setViewModel(binding, data) + // Attaching lifecycleOwner before view model initialization can sometimes cause a // NullPointerException because data might not be attached to the views yet. - binding.lifecycleOwner = getLifecycleOwner() + binding.lifecycleOwner = lifecycleOwner } } } @@ -237,7 +239,7 @@ class BindableAdapter internal constructor( * Constructs a new [BindableAdapter] that supports multiple view types. Each type returned by the * computer should have an associated view binder. * - * Instances of [MultiTypeBuilder] should be instantiated using [newBuilder]. + * Instances of this class can be created by injecting [Factory] and calling [Factory.create]. */ class MultiTypeBuilder>( private val dataClassType: KClass, @@ -250,7 +252,7 @@ class BindableAdapter internal constructor( * Registers a [View] inflater and bind function for views of the specified view type (with * default value [DEFAULT_VIEW_TYPE] for single-view [RecyclerView]s). Note that the viewType * specified here must be properly returned in the [ComputeViewType] function passed into - * [newBuilder]. + * [Factory.create]. * * The inflateView and bindView functions passed in here must not hold any references to UI * objects except those that own the RecyclerView. @@ -331,9 +333,10 @@ class BindableAdapter internal constructor( object : BindableViewHolder(binding.root) { override fun bind(data: T) { setViewModel(binding, transformViewModel(data)) + // Attaching lifecycleOwner before view model initialization can sometimes cause a // NullPointerException because data might not be attached to the views yet. - binding.lifecycleOwner = getLifecycleOwner() + binding.lifecycleOwner = lifecycleOwner } } } diff --git a/app/src/main/java/org/oppia/android/app/testing/ImageRegionSelectionTestActivity.kt b/app/src/main/java/org/oppia/android/app/testing/ImageRegionSelectionTestActivity.kt index ba023d37896..3a79dd9d6af 100644 --- a/app/src/main/java/org/oppia/android/app/testing/ImageRegionSelectionTestActivity.kt +++ b/app/src/main/java/org/oppia/android/app/testing/ImageRegionSelectionTestActivity.kt @@ -5,6 +5,7 @@ import org.oppia.android.R import org.oppia.android.app.activity.ActivityComponentImpl import org.oppia.android.app.activity.InjectableAppCompatActivity import org.oppia.android.app.utility.ClickableAreasImage +import org.oppia.android.app.utility.OnClickableAreaClickedListener /** Test Activity used for testing [ClickableAreasImage] functionality */ class ImageRegionSelectionTestActivity : InjectableAppCompatActivity() { @@ -21,4 +22,16 @@ class ImageRegionSelectionTestActivity : InjectableAppCompatActivity() { ) .commitNow() } + + /** + * Sets a test [OnClickableAreaClickedListener] that will be called when the image region + * maintained by this test activity is clicked. + */ + fun setMockOnClickableAreaClickedListener(listener: OnClickableAreaClickedListener) { + val fragment = + supportFragmentManager.findFragmentById(R.id.test_fragment_placeholder) + as? ImageRegionSelectionTestFragment + ?: throw AssertionError("Expected fragment to be present.") + fragment.mockOnClickableAreaClickedListener = listener + } } diff --git a/app/src/main/java/org/oppia/android/app/testing/ImageRegionSelectionTestFragment.kt b/app/src/main/java/org/oppia/android/app/testing/ImageRegionSelectionTestFragment.kt index 1aaaf62bf34..43c0374a19d 100644 --- a/app/src/main/java/org/oppia/android/app/testing/ImageRegionSelectionTestFragment.kt +++ b/app/src/main/java/org/oppia/android/app/testing/ImageRegionSelectionTestFragment.kt @@ -8,17 +8,21 @@ import android.view.ViewGroup import org.oppia.android.app.fragment.FragmentComponentImpl import org.oppia.android.app.fragment.InjectableFragment import org.oppia.android.app.utility.ClickableAreasImage +import org.oppia.android.app.utility.OnClickableAreaClickedListener +import org.oppia.android.app.utility.RegionClickedEvent import javax.inject.Inject const val IMAGE_REGION_SELECTION_TEST_FRAGMENT_TAG = "image_region_selection_test_fragment" // TODO(#59): Make this fragment only included in relevant tests instead of all prod builds. /** Test Fragment used for testing [ClickableAreasImage] functionality */ -class ImageRegionSelectionTestFragment : InjectableFragment() { +class ImageRegionSelectionTestFragment : InjectableFragment(), OnClickableAreaClickedListener { @Inject lateinit var imageRegionSelectionTestFragmentPresenter: ImageRegionSelectionTestFragmentPresenter + lateinit var mockOnClickableAreaClickedListener: OnClickableAreaClickedListener + override fun onAttach(context: Context) { super.onAttach(context) (fragmentComponent as FragmentComponentImpl).inject(this) @@ -31,4 +35,8 @@ class ImageRegionSelectionTestFragment : InjectableFragment() { ): View? { return imageRegionSelectionTestFragmentPresenter.handleCreateView(inflater, container) } + + override fun onClickableAreaTouched(region: RegionClickedEvent) { + mockOnClickableAreaClickedListener.onClickableAreaTouched(region) + } } diff --git a/app/src/main/java/org/oppia/android/app/testing/ImageRegionSelectionTestFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/testing/ImageRegionSelectionTestFragmentPresenter.kt index 058a8334426..2abb40de558 100644 --- a/app/src/main/java/org/oppia/android/app/testing/ImageRegionSelectionTestFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/testing/ImageRegionSelectionTestFragmentPresenter.kt @@ -3,24 +3,30 @@ package org.oppia.android.app.testing import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.appcompat.app.AppCompatActivity +import androidx.fragment.app.Fragment import org.oppia.android.R import org.oppia.android.app.model.ImageWithRegions.LabeledRegion import org.oppia.android.app.model.Point2d import org.oppia.android.app.player.state.ImageRegionSelectionInteractionView +import org.oppia.android.app.utility.OnClickableAreaClickedListener +import org.oppia.android.databinding.ImageRegionSelectionTestFragmentBinding import javax.inject.Inject /** The presenter for [ImageRegionSelectionTestActivity] */ class ImageRegionSelectionTestFragmentPresenter @Inject constructor( - private val activity: AppCompatActivity + private val fragment: Fragment ) { - fun handleCreateView(inflater: LayoutInflater, container: ViewGroup?): View? { - val view = inflater.inflate(R.layout.image_region_selection_test_fragment, container, false) + val view = + ImageRegionSelectionTestFragmentBinding.inflate( + inflater, container, /* attachToRoot= */ false + ).root with(view) { val clickableAreas: List = getClickableAreas() - view.findViewById(R.id.clickable_image_view) - .setClickableAreas(clickableAreas) + view.findViewById(R.id.clickable_image_view).apply { + setClickableAreas(clickableAreas) + setOnRegionClicked(fragment as OnClickableAreaClickedListener) + } } return view } diff --git a/app/src/main/java/org/oppia/android/app/utility/ClickableAreasImage.kt b/app/src/main/java/org/oppia/android/app/utility/ClickableAreasImage.kt index 30ba360afa7..69d599fd2de 100644 --- a/app/src/main/java/org/oppia/android/app/utility/ClickableAreasImage.kt +++ b/app/src/main/java/org/oppia/android/app/utility/ClickableAreasImage.kt @@ -12,19 +12,19 @@ import org.oppia.android.R import org.oppia.android.app.model.ImageWithRegions import org.oppia.android.app.player.state.ImageRegionSelectionInteractionView import org.oppia.android.app.shim.ViewBindingShim -import org.oppia.android.domain.oppialogger.OppiaLogger -import javax.inject.Inject import kotlin.math.roundToInt -/** - * Helper class to handle clicks on an image along with highlighting the selected region . - */ +/** Helper class to handle clicks on an image along with highlighting the selected region. */ class ClickableAreasImage( private val imageView: ImageRegionSelectionInteractionView, private val parentView: FrameLayout, private val listener: OnClickableAreaClickedListener, - private val bindingInterface: ViewBindingShim + bindingInterface: ViewBindingShim, + private val isAccessibilityEnabled: Boolean, + private val clickableAreas: List ) { + private val defaultRegionView by lazy { bindingInterface.getDefaultRegion(parentView) } + init { imageView.setOnTouchListener { view, motionEvent -> if (motionEvent.action == MotionEvent.ACTION_DOWN) { @@ -34,29 +34,25 @@ class ClickableAreasImage( } } - @Inject - lateinit var oppiaLogger: OppiaLogger - /** * Called when an image is clicked. * - * @param view the original view on which the tap/click occurs. * @param x the relative x coordinate according to image * @param y the relative y coordinate according to image */ private fun onPhotoTap(x: Float, y: Float) { - // Show default region for non-accessibility cases and this will be only called when user taps on unspecified region. - if (!imageView.isAccessibilityEnabled()) { + // Show default region for non-accessibility cases and this will be only called when user taps + // on unspecified region. + if (!isAccessibilityEnabled) { resetRegionSelectionViews() - val defaultRegion = bindingInterface.getDefaultRegion(parentView) - defaultRegion.setBackgroundResource(R.drawable.selected_region_background) - defaultRegion.x = x - defaultRegion.y = y + defaultRegionView.setBackgroundResource(R.drawable.selected_region_background) + defaultRegionView.x = x + defaultRegionView.y = y listener.onClickableAreaTouched(DefaultRegionClickedEvent()) } } - /** Function to remove the background from the views.*/ + /** Function to remove the background from the views. */ private fun resetRegionSelectionViews() { parentView.forEachIndexed { index: Int, childView: View -> // Remove any previously selected region excluding 0th index(image view) @@ -66,12 +62,12 @@ class ClickableAreasImage( } } - /** Get X co-ordinate scaled according to image.*/ + /** Get X co-ordinate scaled according to image. */ private fun getXCoordinate(x: Float): Float { return x * getImageViewContentWidth() } - /** Get Y co-ordinate scaled according to image.*/ + /** Get Y co-ordinate scaled according to image. */ private fun getYCoordinate(y: Float): Float { return y * getImageViewContentHeight() } @@ -84,76 +80,65 @@ class ClickableAreasImage( return imageView.height - imageView.paddingTop - imageView.paddingBottom } - /** Add selectable regions to [FrameLayout].*/ + /** Add selectable regions to [FrameLayout]. */ fun addRegionViews() { - parentView.let { - if (it.childCount > 2) { - try { - it.removeViews(2, it.childCount - 1) // remove all other views - } catch (e: IndexOutOfBoundsException) { - if (::oppiaLogger.isInitialized) - oppiaLogger.e( - "ClickableAreaImage", - "Throws exception on Index out of bound", - e - ) + // Remove all views other than the default region & selectable image. + parentView.children.filter { + it.id != imageView.id && it.id != defaultRegionView.id + }.forEach(parentView::removeView) + clickableAreas.forEach { clickableArea -> + val imageRect = RectF( + getXCoordinate(clickableArea.region.area.upperLeft.x), + getYCoordinate(clickableArea.region.area.upperLeft.y), + getXCoordinate(clickableArea.region.area.lowerRight.x), + getYCoordinate(clickableArea.region.area.lowerRight.y) + ) + val layoutParams = FrameLayout.LayoutParams( + imageRect.width().roundToInt(), + imageRect.height().roundToInt() + ) + val newView = View(parentView.context) + // ClickableArea coordinates are not laid-out properly in RTL. The image region coordinates + // are from left-to-right with an upper left origin and touch coordinates from Android start + // from the right in RTL mode. Thus, to avoid this situation, force layout direction to LTR in + // all situations. + ViewCompat.setLayoutDirection(parentView, ViewCompat.LAYOUT_DIRECTION_LTR) + newView.layoutParams = layoutParams + newView.x = imageRect.left + newView.y = imageRect.top + newView.isClickable = true + newView.isFocusable = true + newView.isFocusableInTouchMode = true + newView.tag = clickableArea.label + newView.setOnTouchListener { _, event -> + if (event.action == MotionEvent.ACTION_DOWN) { + showOrHideRegion(newView, clickableArea) } + return@setOnTouchListener true } - imageView.getClickableAreas().forEach { clickableArea -> - val imageRect = RectF( - getXCoordinate(clickableArea.region.area.upperLeft.x), - getYCoordinate(clickableArea.region.area.upperLeft.y), - getXCoordinate(clickableArea.region.area.lowerRight.x), - getYCoordinate(clickableArea.region.area.lowerRight.y) - ) - val layoutParams = FrameLayout.LayoutParams( - imageRect.width().roundToInt(), - imageRect.height().roundToInt() - ) - val newView = View(it.context) - // ClickableArea coordinates are not laid-out properly in RTL. The image region coordinates are - // from left-to-right with an upper left origin and touch coordinates from Android start from the - // right in RTL mode. Thus, to avoid this situation, force layout direction to LTR in all situations. - ViewCompat.setLayoutDirection(it, ViewCompat.LAYOUT_DIRECTION_LTR) - newView.layoutParams = layoutParams - newView.x = imageRect.left - newView.y = imageRect.top - newView.isClickable = true - newView.isFocusable = true - newView.isFocusableInTouchMode = true - newView.tag = clickableArea.label - newView.setOnTouchListener { _, event -> - if (event.action == MotionEvent.ACTION_DOWN) { - showOrHideRegion(newView, clickableArea) - } - return@setOnTouchListener true + if (isAccessibilityEnabled) { + // Make default region visibility gone when talkback enabled to avoid any accidental touch. + defaultRegionView.isVisible = false + newView.setOnClickListener { + showOrHideRegion(newView, clickableArea) } - if (imageView.isAccessibilityEnabled()) { - // Make default region visibility gone when talkback enabled to avoid any accidental touch. - val defaultRegion = bindingInterface.getDefaultRegion(parentView) - defaultRegion.isVisible = false - newView.setOnClickListener { - showOrHideRegion(newView, clickableArea) - } - } - newView.contentDescription = clickableArea.contentDescription - it.addView(newView) } + newView.contentDescription = clickableArea.contentDescription + parentView.addView(newView) + } - // Ensure that the children views are properly computed. The specific flow below is - // recommended by https://stackoverflow.com/a/42430695/3689782 where it's also explained in - // great detail. The 'post' seems necessary since, from observation, requesting layout & - // invalidation doesn't always work (perhaps since this method can be called during a layout - // step), so posting ensures that the views are eventually computed. It's not obvious why - // Android sometimes doesn't compute the region view dimensions, but it results in the - // interaction being non-interactive (though it's recoverable with back & forward navigation - // or rotation, this isn't likely to be obvious to learners and it's a generally poor user - // experience). - it.post { - it.children.forEach(View::forceLayout) - it.invalidate() - it.requestLayout() - } + // Ensure that the children views are properly computed. The specific flow below is recommended + // by https://stackoverflow.com/a/42430695/3689782 where it's also explained in great detail. + // The 'post' seems necessary since, from observation, requesting layout & invalidation doesn't + // always work (perhaps since this method can be called during a layout step), so posting + // ensures that the views are eventually computed. It's not obvious why Android sometimes + // doesn't compute the region view dimensions, but it results in the interaction being + // non-interactive (though it's recoverable with back & forward navigation or rotation, this + // isn't likely to be obvious to learners and it's a generally poor user experience). + parentView.post { + parentView.children.forEach(View::forceLayout) + parentView.invalidate() + parentView.requestLayout() } } diff --git a/app/src/main/res/layout/image_region_selection_test_fragment.xml b/app/src/main/res/layout/image_region_selection_test_fragment.xml index 5ff07314181..01bd0230c13 100644 --- a/app/src/main/res/layout/image_region_selection_test_fragment.xml +++ b/app/src/main/res/layout/image_region_selection_test_fragment.xml @@ -1,21 +1,26 @@ - + xmlns:tools="http://schemas.android.com/tools"> - + tools:context=".app.testing.ImageRegionSelectionTestActivity"> - - + + + + + diff --git a/app/src/sharedTest/java/org/oppia/android/app/testing/ImageRegionSelectionInteractionViewTest.kt b/app/src/sharedTest/java/org/oppia/android/app/testing/ImageRegionSelectionInteractionViewTest.kt index a29f03d5667..246a344972b 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/testing/ImageRegionSelectionInteractionViewTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/testing/ImageRegionSelectionInteractionViewTest.kt @@ -27,7 +27,7 @@ import org.mockito.Captor import org.mockito.Mock import org.mockito.Mockito.times import org.mockito.Mockito.verify -import org.mockito.MockitoAnnotations +import org.mockito.junit.MockitoJUnit import org.oppia.android.R import org.oppia.android.app.activity.ActivityComponent import org.oppia.android.app.activity.ActivityComponentFactory @@ -39,7 +39,6 @@ import org.oppia.android.app.application.ApplicationStartupListenerModule import org.oppia.android.app.application.testing.TestingBuildFlavorModule import org.oppia.android.app.devoptions.DeveloperOptionsModule import org.oppia.android.app.devoptions.DeveloperOptionsStarterModule -import org.oppia.android.app.player.state.ImageRegionSelectionInteractionView import org.oppia.android.app.player.state.StateFragment import org.oppia.android.app.player.state.itemviewmodel.SplitScreenInteractionModule import org.oppia.android.app.shim.ViewBindingShimModule @@ -82,6 +81,7 @@ import org.oppia.android.domain.topic.PrimeTopicAssetsControllerModule import org.oppia.android.domain.workmanager.WorkManagerConfigurationModule import org.oppia.android.testing.OppiaTestRule import org.oppia.android.testing.RunOn +import org.oppia.android.testing.TestImageLoaderModule import org.oppia.android.testing.TestLogReportingModule import org.oppia.android.testing.TestPlatform import org.oppia.android.testing.junit.InitializeDefaultLocaleRule @@ -101,8 +101,8 @@ import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule -import org.oppia.android.util.parser.image.GlideImageLoaderModule import org.oppia.android.util.parser.image.ImageParsingModule +import org.oppia.android.util.parser.image.TestGlideImageLoader import org.robolectric.annotation.Config import org.robolectric.annotation.LooperMode import javax.inject.Inject @@ -116,39 +116,29 @@ import javax.inject.Singleton qualifiers = "port-xxhdpi" ) class ImageRegionSelectionInteractionViewTest { - @get:Rule - val initializeDefaultLocaleRule = InitializeDefaultLocaleRule() + @get:Rule val initializeDefaultLocaleRule = InitializeDefaultLocaleRule() + @get:Rule val oppiaTestRule = OppiaTestRule() + @get:Rule val mockitoRule = MockitoJUnit.rule() - @Inject - lateinit var context: Context + @Mock lateinit var onClickableAreaClickedListener: OnClickableAreaClickedListener + @Captor lateinit var regionClickedEvent: ArgumentCaptor - @Mock - lateinit var onClickableAreaClickedListener: OnClickableAreaClickedListener - - @Captor - lateinit var regionClickedEvent: ArgumentCaptor - - @get:Rule - val oppiaTestRule = OppiaTestRule() + @Inject lateinit var context: Context + @Inject lateinit var imageLoader: TestGlideImageLoader @Before fun setUp() { - setUpTestApplicationComponent() - MockitoAnnotations.initMocks(this) - } - - private fun setUpTestApplicationComponent() { ApplicationProvider.getApplicationContext().inject(this) + imageLoader.arrangeBitmap("test_image_url.drawable", R.drawable.testing_fraction) } @Test // TODO(#1611): Fix ImageRegionSelectionInteractionViewTest @RunOn(TestPlatform.ESPRESSO) fun testImageRegionSelectionInteractionView_clickRegion3_region3Clicked() { - launch(ImageRegionSelectionTestActivity::class.java).use { - it.onActivity { - it.findViewById(R.id.clickable_image_view) - .setListener(onClickableAreaClickedListener) + launch(ImageRegionSelectionTestActivity::class.java).use { scenario -> + scenario.onActivity { activity -> + activity.setMockOnClickableAreaClickedListener(onClickableAreaClickedListener) } onView(withId(R.id.clickable_image_view)).perform( clickPoint(pointX = 0.3f, pointY = 0.3f) @@ -171,10 +161,9 @@ class ImageRegionSelectionInteractionViewTest { // TODO(#1611): Fix ImageRegionSelectionInteractionViewTest @RunOn(TestPlatform.ESPRESSO) fun testImageRegionSelectionInteractionView_clickRegion3_clickRegion2_region2Clicked() { - launch(ImageRegionSelectionTestActivity::class.java).use { - it.onActivity { - it.findViewById(R.id.clickable_image_view) - .setListener(onClickableAreaClickedListener) + launch(ImageRegionSelectionTestActivity::class.java).use { scenario -> + scenario.onActivity { activity -> + activity.setMockOnClickableAreaClickedListener(onClickableAreaClickedListener) } onView(withId(R.id.clickable_image_view)).perform( clickPoint(pointX = 0.3f, pointY = 0.3f) @@ -213,10 +202,9 @@ class ImageRegionSelectionInteractionViewTest { // TODO(#1611): Fix ImageRegionSelectionInteractionViewTest @RunOn(TestPlatform.ESPRESSO) fun testImageRegionSelectionInteractionView_clickOnDefaultRegion_defaultRegionClicked() { - launch(ImageRegionSelectionTestActivity::class.java).use { - it.onActivity { - it.findViewById(R.id.clickable_image_view) - .setListener(onClickableAreaClickedListener) + launch(ImageRegionSelectionTestActivity::class.java).use { scenario -> + scenario.onActivity { activity -> + activity.setMockOnClickableAreaClickedListener(onClickableAreaClickedListener) } onView(withId(R.id.clickable_image_view)).perform( clickPoint(pointX = 0.0f, pointY = 0.0f) @@ -235,10 +223,9 @@ class ImageRegionSelectionInteractionViewTest { @Test @RunOn(TestPlatform.ESPRESSO) fun testView_withTalkbackEnabled_clickRegion3_clickRegion2_region2Clicked() { - launch(ImageRegionSelectionTestActivity::class.java).use { - it.onActivity { - it.findViewById(R.id.clickable_image_view) - .setListener(onClickableAreaClickedListener) + launch(ImageRegionSelectionTestActivity::class.java).use { scenario -> + scenario.onActivity { activity -> + activity.setMockOnClickableAreaClickedListener(onClickableAreaClickedListener) } onView(withId(R.id.clickable_image_view)).perform( clickPoint(pointX = 0.3f, pointY = 0.3f) @@ -276,10 +263,9 @@ class ImageRegionSelectionInteractionViewTest { @Test @RunOn(TestPlatform.ESPRESSO) fun testImageRegionSelectionInteractionView_withTalkbackEnabled_clickRegion3_region3Clicked() { - launch(ImageRegionSelectionTestActivity::class.java).use { - it.onActivity { - it.findViewById(R.id.clickable_image_view) - .setListener(onClickableAreaClickedListener) + launch(ImageRegionSelectionTestActivity::class.java).use { scenario -> + scenario.onActivity { activity -> + activity.setMockOnClickableAreaClickedListener(onClickableAreaClickedListener) } onView(withId(R.id.clickable_image_view)).perform( clickPoint(pointX = 0.3f, pointY = 0.3f) @@ -306,9 +292,8 @@ class ImageRegionSelectionInteractionViewTest { @Ignore("Move to Robolectric") fun testView_withTalkbackEnabled_clickOnDefaultRegion_defaultRegionNotClicked() { launch(ImageRegionSelectionTestActivity::class.java).use { scenario -> - scenario.onActivity { - it.findViewById(R.id.clickable_image_view) - .setListener(onClickableAreaClickedListener) + scenario.onActivity { activity -> + activity.setMockOnClickableAreaClickedListener(onClickableAreaClickedListener) } onView(withId(R.id.clickable_image_view)).perform( clickPoint(pointX = 0.0f, pointY = 0.0f) @@ -325,11 +310,10 @@ class ImageRegionSelectionInteractionViewTest { // TODO(#1611): Fix ImageRegionSelectionInteractionViewTest @RunOn(TestPlatform.ESPRESSO) fun testImageRegionSelectionInteractionView_rtl_clickRegion3_region3Clicked() { - launch(ImageRegionSelectionTestActivity::class.java).use { - it.onActivity { - it.window.decorView.layoutDirection = ViewCompat.LAYOUT_DIRECTION_RTL - it.findViewById(R.id.clickable_image_view) - .setListener(onClickableAreaClickedListener) + launch(ImageRegionSelectionTestActivity::class.java).use { scenario -> + scenario.onActivity { activity -> + activity.window.decorView.layoutDirection = ViewCompat.LAYOUT_DIRECTION_RTL + activity.setMockOnClickableAreaClickedListener(onClickableAreaClickedListener) } onView(withId(R.id.clickable_image_view)).perform( clickPoint(pointX = 0.3f, pointY = 0.3f) @@ -352,11 +336,10 @@ class ImageRegionSelectionInteractionViewTest { // TODO(#1611): Fix ImageRegionSelectionInteractionViewTest @RunOn(TestPlatform.ESPRESSO) fun testImageRegionSelectionInteractionView_rtl_clickRegion3_clickRegion2_region2Clicked() { - launch(ImageRegionSelectionTestActivity::class.java).use { - it.onActivity { - it.window.decorView.layoutDirection = ViewCompat.LAYOUT_DIRECTION_RTL - it.findViewById(R.id.clickable_image_view) - .setListener(onClickableAreaClickedListener) + launch(ImageRegionSelectionTestActivity::class.java).use { scenario -> + scenario.onActivity { activity -> + activity.window.decorView.layoutDirection = ViewCompat.LAYOUT_DIRECTION_RTL + activity.setMockOnClickableAreaClickedListener(onClickableAreaClickedListener) } onView(withId(R.id.clickable_image_view)).perform( clickPoint(pointX = 0.3f, pointY = 0.3f) @@ -402,7 +385,7 @@ class ImageRegionSelectionInteractionViewTest { ItemSelectionInputModule::class, MultipleChoiceInputModule::class, NumberWithUnitsRuleModule::class, NumericInputRuleModule::class, TextInputRuleModule::class, DragDropSortInputModule::class, ImageClickInputModule::class, InteractionsModule::class, - GcsResourceModule::class, GlideImageLoaderModule::class, ImageParsingModule::class, + GcsResourceModule::class, TestImageLoaderModule::class, ImageParsingModule::class, HtmlParserEntityTypeModule::class, QuestionModule::class, TestLogReportingModule::class, AccessibilityTestModule::class, LogStorageModule::class, CachingTestModule::class, PrimeTopicAssetsControllerModule::class, ExpirationMetaDataRetrieverModule::class, diff --git a/utility/src/main/java/org/oppia/android/util/parser/image/TestGlideImageLoader.kt b/utility/src/main/java/org/oppia/android/util/parser/image/TestGlideImageLoader.kt index 82acfc150b6..7d1f4cbb7dd 100644 --- a/utility/src/main/java/org/oppia/android/util/parser/image/TestGlideImageLoader.kt +++ b/utility/src/main/java/org/oppia/android/util/parser/image/TestGlideImageLoader.kt @@ -1,7 +1,9 @@ package org.oppia.android.util.parser.image +import android.content.Context import android.graphics.Bitmap import android.graphics.drawable.Drawable +import androidx.annotation.DrawableRes import org.oppia.android.util.parser.math.MathModel import org.oppia.android.util.parser.svg.BlockPictureDrawable import javax.inject.Inject @@ -14,8 +16,10 @@ import javax.inject.Singleton */ @Singleton class TestGlideImageLoader @Inject constructor( - private val glideImageLoader: GlideImageLoader + private val glideImageLoader: GlideImageLoader, + private val context: Context ) : ImageLoader { + private val availableBitmaps = mutableMapOf() private val loadedBitmaps = mutableListOf() private val loadedBlockSvgs = mutableListOf() private val loadedTextSvgs = mutableListOf() @@ -27,7 +31,13 @@ class TestGlideImageLoader @Inject constructor( transformations: List ) { loadedBitmaps += imageUrl - glideImageLoader.loadBitmap(imageUrl, target, transformations) + val filename = imageUrl.substringAfterLast('/') + if (filename in availableBitmaps) { + check(target is ImageViewTarget) { + "Only ImageViewTarget-type loads are supported to be overwritten in TestGlideImageLoader." + } + target.imageView.setImageResource(availableBitmaps.getValue(filename)) + } else glideImageLoader.loadBitmap(imageUrl, target, transformations) } override fun loadBlockSvg( @@ -74,6 +84,20 @@ class TestGlideImageLoader @Inject constructor( glideImageLoader.loadMathDrawable(rawLatex, lineHeight, useInlineRendering, target) } + /** + * Sets a test bitmap to load when [loadbitmap] is called, based on a specified filename. + * + * The image loaded will correspond to [imageDrawableResId] instead of being loaded from the + * requested image URL. + * + * Subsequent calls to this method will override any previous arrangements. Multiple filenames may + * point to the same drawable IDs. Referenced drawables do not actually need to be bitmaps (they + * can be any types of drawable). + */ + fun arrangeBitmap(filename: String, @DrawableRes imageDrawableResId: Int) { + availableBitmaps[filename] = imageDrawableResId + } + /** * Returns the list of image URLs that have been loaded as bitmaps since the start of the * application. From 8ff532b823b2757621a5d4ec9715935e25dbf9e3 Mon Sep 17 00:00:00 2001 From: kevingitonga Date: Mon, 26 Sep 2022 12:09:14 +0300 Subject: [PATCH 32/34] Fix lint issues after resolving merge conflicts on CI and add lessons_chapter_view.xml which is not available on my branch even after several sync's causing build errors. --- .../lessons/TopicLessonsFragmentPresenter.kt | 5 +- .../main/res/layout/lessons_chapter_view.xml | 65 +++++++++++++++++++ 2 files changed, 66 insertions(+), 4 deletions(-) create mode 100644 app/src/main/res/layout/lessons_chapter_view.xml diff --git a/app/src/main/java/org/oppia/android/app/topic/lessons/TopicLessonsFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/topic/lessons/TopicLessonsFragmentPresenter.kt index 81e45856b5b..f3e12b5562f 100644 --- a/app/src/main/java/org/oppia/android/app/topic/lessons/TopicLessonsFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/topic/lessons/TopicLessonsFragmentPresenter.kt @@ -17,10 +17,7 @@ import org.oppia.android.app.model.StorySummary import org.oppia.android.app.recyclerview.BindableAdapter import org.oppia.android.app.topic.RouteToResumeLessonListener import org.oppia.android.app.topic.RouteToStoryListener -import org.oppia.android.databinding.LessonsCompletedChapterViewBinding -import org.oppia.android.databinding.LessonsInProgressChapterViewBinding -import org.oppia.android.databinding.LessonsLockedChapterViewBinding -import org.oppia.android.databinding.LessonsNotStartedChapterViewBinding +import org.oppia.android.databinding.LessonsChapterViewBinding import org.oppia.android.databinding.TopicLessonsFragmentBinding import org.oppia.android.databinding.TopicLessonsStorySummaryBinding import org.oppia.android.databinding.TopicLessonsTitleBinding diff --git a/app/src/main/res/layout/lessons_chapter_view.xml b/app/src/main/res/layout/lessons_chapter_view.xml new file mode 100644 index 00000000000..55a1254782c --- /dev/null +++ b/app/src/main/res/layout/lessons_chapter_view.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + From 69603ff1286117bd73b963fdc8968f39d6b19508 Mon Sep 17 00:00:00 2001 From: kevingitonga Date: Thu, 29 Sep 2022 15:34:47 +0300 Subject: [PATCH 33/34] Revert deleted deltas. --- .../lessons/TopicLessonsFragmentPresenter.kt | 53 ++++++++++++++++--- 1 file changed, 47 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/org/oppia/android/app/topic/lessons/TopicLessonsFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/topic/lessons/TopicLessonsFragmentPresenter.kt index f3e12b5562f..bdb858ff1bd 100644 --- a/app/src/main/java/org/oppia/android/app/topic/lessons/TopicLessonsFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/topic/lessons/TopicLessonsFragmentPresenter.kt @@ -17,7 +17,10 @@ import org.oppia.android.app.model.StorySummary import org.oppia.android.app.recyclerview.BindableAdapter import org.oppia.android.app.topic.RouteToResumeLessonListener import org.oppia.android.app.topic.RouteToStoryListener -import org.oppia.android.databinding.LessonsChapterViewBinding +import org.oppia.android.databinding.LessonsCompletedChapterViewBinding +import org.oppia.android.databinding.LessonsInProgressChapterViewBinding +import org.oppia.android.databinding.LessonsLockedChapterViewBinding +import org.oppia.android.databinding.LessonsNotStartedChapterViewBinding import org.oppia.android.databinding.TopicLessonsFragmentBinding import org.oppia.android.databinding.TopicLessonsStorySummaryBinding import org.oppia.android.databinding.TopicLessonsTitleBinding @@ -225,11 +228,49 @@ class TopicLessonsFragmentPresenter @Inject constructor( } private fun createChapterRecyclerViewAdapter(): BindableAdapter { - return singleTypeBuilderFactory.create() - .registerViewDataBinderWithSameModelType( - inflateDataBinding = LessonsChapterViewBinding::inflate, - setViewModel = LessonsChapterViewBinding::setViewModel - ).build() + return multiTypeBuilderFactory.create { viewModel -> + when (viewModel.chapterPlayState) { + ChapterPlayState.NOT_PLAYABLE_MISSING_PREREQUISITES -> ChapterViewType.CHAPTER_LOCKED + ChapterPlayState.COMPLETED -> ChapterViewType.CHAPTER_COMPLETED + ChapterPlayState.IN_PROGRESS_SAVED, ChapterPlayState.IN_PROGRESS_NOT_SAVED, + ChapterPlayState.STARTED_NOT_COMPLETED, ChapterPlayState.COMPLETION_STATUS_UNSPECIFIED + -> ChapterViewType.CHAPTER_IN_PROGRESS + ChapterPlayState.NOT_STARTED -> ChapterViewType.CHAPTER_NOT_STARTED + ChapterPlayState.UNRECOGNIZED -> throw IllegalArgumentException("Play state unknown") + } + } + .registerViewDataBinder( + viewType = ChapterViewType.CHAPTER_LOCKED, + inflateDataBinding = LessonsLockedChapterViewBinding::inflate, + setViewModel = LessonsLockedChapterViewBinding::setViewModel, + transformViewModel = { it } + ) + .registerViewDataBinder( + viewType = ChapterViewType.CHAPTER_COMPLETED, + inflateDataBinding = LessonsCompletedChapterViewBinding::inflate, + setViewModel = LessonsCompletedChapterViewBinding::setViewModel, + transformViewModel = { it } + ) + .registerViewDataBinder( + viewType = ChapterViewType.CHAPTER_NOT_STARTED, + inflateDataBinding = LessonsNotStartedChapterViewBinding::inflate, + setViewModel = LessonsNotStartedChapterViewBinding::setViewModel, + transformViewModel = { it } + ) + .registerViewDataBinder( + viewType = ChapterViewType.CHAPTER_IN_PROGRESS, + inflateDataBinding = LessonsInProgressChapterViewBinding::inflate, + setViewModel = LessonsInProgressChapterViewBinding::setViewModel, + transformViewModel = { it } + ) + .build() + } + + private enum class ChapterViewType { + CHAPTER_NOT_STARTED, + CHAPTER_COMPLETED, + CHAPTER_LOCKED, + CHAPTER_IN_PROGRESS } fun storySummaryClicked(storySummary: StorySummary) { From c8eb1d64422ca2b55df6bac207378a4e730a6f1e Mon Sep 17 00:00:00 2001 From: kevingitonga Date: Mon, 3 Oct 2022 10:37:18 +0300 Subject: [PATCH 34/34] Remove lessons_chapter_view.xml since it is no longer required. --- .../main/res/layout/lessons_chapter_view.xml | 65 ------------------- 1 file changed, 65 deletions(-) delete mode 100644 app/src/main/res/layout/lessons_chapter_view.xml diff --git a/app/src/main/res/layout/lessons_chapter_view.xml b/app/src/main/res/layout/lessons_chapter_view.xml deleted file mode 100644 index 55a1254782c..00000000000 --- a/app/src/main/res/layout/lessons_chapter_view.xml +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - -