Investigate RemoteMediator for Notifications#6606
Open
uwemartin-lgtm wants to merge 40 commits into
Open
Conversation
…unit testing: wrapper for preferences and repository
…title, header, body or (secondary) link are matched
…ies to view model required for this test
…del using database
…entation to "Legacy" and new implementation to "Refactored"
…of filter selection
…on end has not been reached yet
…st cases accordingly
…anged performance test for refactored implementation to not use in-memory database.
… and reused the data for both, legacy and refactored version
…ultiple emissions of same data)
…updated database version number and added migration.
…itial load. Moved string literals to Constants.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What does this do?
Apply the
Paging3library including a remote mediator and updating the Notification view model to use a flow-based approach.Why is this needed?
See ticket T403106
Technical background
Current implementation
The current implementation uses an in-memory sorting and filtering. The Room database is used for storing all notifications received from the server, but the app reloads ALL of that data into memory before commencing the filtering and sorting.
The current implementation does reuse pages already stored in the database but it does not persist the pagination token. The database is only reset when the user logs out.
Paging3 / RemoteMediator
The library intends to only load a fraction of the data necessary to display.
I noticed that (in the intended scenario) it loads a couple of pages (in this case 4 pages with 50 elements).
When the user scrolls to the end of the available data, the library coordinates loading of more data and buffering in the local database. Following the functionality of the current implementation where ALL data is loaded to the local database, I updated the
NotificationRemoteMediatorimplementation to also implement the loading and local storing of all notifications from the server (call tosyncAll). This means that the refactored code does not take full advantage of thePaging3library (but easily could if deviation of from functionality from current implementation is preferred).Overview of architectural update
Architecture of current implementation
Main architectural difference
The filtering/sorting logic moves from the in-memory-processing in the view model as done in the legacy code to the database DAO which provides the
PagingSource.This made the database query significantly more complex. but still feasible in terms of performance.
Implementing a custom paging source logic would also require implementing a custom invalidation pipeline.
Based on feedback to this PR or with a separate PR, I can also look into that.
The refactored implementation also does not check the availability of the server using
WikipediaApp.instance.isOnlineIt relies on the remote mediator exception handling.
The refactored code introduces persistence for the pagination token (although the token has to be reset to null during initial load to avoid ignoring "fresh" pages - see open points).
The search bar is now added into the
NotificationContainerListby the view model instead of relying on the Activity. This results also in slightly different results for unit testing.Introduction of interfaces
The PR introduces a set of interfaces to enable mocking:
NotificationViewModel: allowing flexible use of current implementation "legacy" and refactored code for unit testing exclusively.NotificationRepository: allowing mock ("fake") repositories for unit testing and instrumented (performance) testsNotificationFilterHelper: allowing mocking of reference wikis and categories/types.NotificationPreferences: allowing mocking of user selection for filteringMain updates to git repo
NotificationLegacyViewModelImplNotficationRefactoredViewModelImplNotificationRepository,NotificationFilterHelperandNotificationPreferencesNotificationViewModelFactory)Change of functionality
The refactored application uses the following approach for updating the counts on the "All" and "Mentions" tab: With each page loaded, the count increases (also visible). As long as the end of the data has not been reached, a "+"-sign is added to the counts to indicate the number of notifications currently loaded. When all data is loaded, the numbers are shown without a "+" sign. This approach would also allow for (if desired) only load a subset of all notifications from the server and making this visible to the user.
Testing
The PR contains both unit testing (running on developer machine) and performance testing (instrumented tests to run on physical device).
Unit testing
The unit testing focusses on checking the filter and sorting functionality. Based on the value of
legacyit can run the current implementation or the refactored code to facilitate comparison.In addition to the filter and sorting functionality, the unit test cases check the refactored code for
Performance testing
Purpose: Evaluate the performance of the refactored code compared to the current implementation.
The tests reside in different test classes in the androidTest folder (combining them did not make sense due to the difference in code structure). However, the tests use a common stimuli generator which resides as a separate class in the same folder.
For the performance testing I used 1000 notifications in 100 iterations with filter setting that filtered down to 28+1 notifications (legacy code only provides 28).
The detailed results are mentioned in the linked Phabricator ticket. Basically, the median execution time went from 140 milliseconds to around 120 milliseconds.
Implementation updates
app/build.gradleand related entry inlibs.versions.tomlAppDatabaseTest.ktto cover also the additional table for the remote key persistenceConstants.ktwhich are used across several filesAppDatabase.ktand added migration scheme. Required for persistence of the remote key.MwQueryResponse.ktto access information whether the end of pagination has already been reached with one specific request: Adding the „notification continue“ entrynotcontinue.NotificationActivityin various places to handle the flow-based view model. The activity now listens to bothuiStateandnotificationFlowprovided by the view model. Changed to using Paging adapter forRecyclerViewintroducing a custom diffing class. Also, communication with theNotificationFilterActivityhas been extended to discover changes of user filter preferences (potential bug in current implementation). Furthermore, update of the loading indicatorProgressbarhad to be adapted.NotificationFilterActivity: Extending communication withNotificationActivityUsage of ofNotificationFilterHelper.NotificationViewModeland renamed the file toNotificationViewModelImpl.NotificationRepositoryinto an interface where the implementation resides inNotificationRepositoryImplNotificationRepositoryinto an interface where the implementation resides inNotificationRefactoredRepositoryImplandNotificationLegacyRepositoryImplequalsandhashCodefunctionality toNotification.ktbecause the diffing class in the activity needs that.Provisions for manual testing
In
NotificationRepositoryImpl.ktyou can override the usage of the API to retrieve data from the server and instead use mock data to experience the dynamic when using the app./* --- MOCK DATA GENERATOR FOR MANUAL TESTING ---Open questions/issues/todos
Pending review comments/maturity of this PR:
NotificationHelper(separate implementation file and companion object inNotificationHelperActivitydue to usage in legacy code)PagingSourcePhabricator:
https://phabricator.wikimedia.org/T...