Skip to content

Migrated bookmarks locations to Kotlin and adapt room database #6148

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 36 commits into from
Feb 24, 2025

Conversation

Saifuddin53
Copy link
Contributor

@Saifuddin53 Saifuddin53 commented Jan 20, 2025

Part of: #6017
Part of: #5928

This commit migrates the bookmark location logic to Kotlin, enhancing code maintainability and readability.

-   Removes the `BookmarkLocationsContentProvider`, `BookmarkLocationsController`, and `BookmarkLocationsDao` Java classes.
-   Creates `BookmarkLocationsDao.kt` and `BookmarkLocationsContentProvider.kt` in Kotlin.
-   Migrates the logic from `BookmarkLocationsFragment.java` to `BookmarkLocationsFragment.kt`.
-   Updates test files to reflect these changes.
-   Addresses associated code review comments.
This commit migrates the bookmark locations functionality from a custom content provider to Room database.

Key changes:

*   **Removal of `BookmarkLocationsContentProvider`:** This class, which previously handled data storage, has been removed.
*   **Introduction of `BookmarksLocations`:** This data class now represents a bookmarked location, serving as the Room entity.
*   **Creation of `BookmarkLocationsDao`:** This Room DAO handles database interactions for bookmark locations, including:
    *   Adding, deleting, and querying bookmarked locations.
    *   Checking if a location is already bookmarked.
    *   Updating the bookmark status of a location.
    *   Retrieving all bookmarked locations as `Place` objects.
*   **`BookmarkLocationsViewModel`:** Added to manage the data layer for bookmark locations
*   **`NearbyUtil`:** Created a Util class for Nearby to manage the bookmark locations.
*   **Updates in `PlaceAdapter` and `PlaceAdapterDelegate`:** These classes have been modified to work with the new Room-based data layer.
*   **Updates in `AppDatabase`:** The database now includes `BookmarksLocations` as an entity and exposes the `bookmarkLocationsDao`.
*   **Updates in `FragmentBuilderModule` and `CommonsApplicationModule`**: for DI
*   **Removal of `DBOpenHelper` upgrade for locations**: as it is no longer needed
* **Updates in `NearbyParentFragmentPresenter`**: refactored the logic to use the dao functions
* **Updates in `NearbyParentFragment`**: refactored the logic to use the util and dao functions
* **Update in `BookmarkLocationsController`**: removed as its no longer needed
* **Add `toPlace` and `toBookmarksLocations`**: extension functions to map between data class and entities
* **Update in `CommonsApplication`**: to remove old db table.
This commit includes the following changes:

-   Updates the database version to 20.
-   Refactors bookmark location handling within `NearbyParentFragment` to improve logic and efficiency.
-   Introduces `getBookmarkLocationExists` in `NearbyUtil` to directly update bookmark icons in the bottom sheet adapter.
-   Removes unused provider.
-   Adjusts `BookmarkLocationsFragment`'s `PlaceAdapter` to use `lifecycleScope` for better coroutine management.
-   Refactors `updateBookmarkLocation` in `NearbyParentFragmentPresenter`.
…ation each time in the bookmark

* Update bookmark button image in `BottomSheetAdapter`
* Add new toggle function to `BottomSheetAdapter`
* Call the toggle function in `NearbyParentFragment`
*   `BookmarkLocationsController`: Changed to use `Flow` to load bookmarked locations.
*   `BookmarkLocationsDao`: Changed to return `Flow` of bookmark location instead of a list.
*   `BookmarkLocationsFragment`: Used `LifecycleScope` to collect data from flow and display it.
*   Removed unused `getAllBookmarkLocations()` from `BookmarkLocationsViewModel`.
*   Used `map` in `BookmarkLocationsDao` to convert from `BookmarksLocations` to `Place` list.
*   BookmarkLocationsController: Changed `loadFavoritesLocations` to be a suspend function.
*   BookmarkLocationsFragment: Updated the `initList` function to call the controller's suspend function.
*   BookmarkLocationsDao: Changed `getAllBookmarksLocations` and `getAllBookmarksLocationsPlace` to be suspend functions.
…utines

*  Migrated `bookmarkDao!!.getAllBookmarksLocations()` to `bookmarkDao!!.getAllBookmarksLocationsPlace()` and added `runBlocking`
*  Added `runBlocking` for `loadBookmarkedLocations()` and `testInitNonEmpty()`
*  Added `runBlocking` for `getAllBookmarksLocations()` and update `updateMapMarkers`

These changes improve the test structure by ensuring the database functions are executed within a coroutine context.
…sDao

*   Moved `initList` to `BookmarkLocationsFragment` for better lifecycle handling.
*   Added test case for `BookmarkLocationsFragment`'s onResume.
*   Added test cases for `BookmarkLocationsDao` operations like adding, retrieving, finding, deleting and updating bookmarks.
…s not null

* BookmarkLocationsFragment: load favorites locations only when view is not null.
* BookmarkLocationsFragmentTest: added spy and verify to test onResume() call initList() method.
*   `AppDatabase`: Updated to use room migration.
*   `CommonsApplicationModule`: added migration from version 19 to 20 to `appDatabase`
@nicolas-raoul
Copy link
Member

Is this still a draft? :-)

@Saifuddin53
Copy link
Contributor Author

Hii @nicolas-raoul, the migration for both Kotlin and Room is complete, but I’m encountering an issue: during the migration to Room, the existing data in the SQL database is being deleted. Is it necessary to preserve that data as well? :-)

@nicolas-raoul
Copy link
Member

Data migration would be ideal. Is it difficult?

@Saifuddin53
Copy link
Contributor Author

Not too difficult @nicolas-raoul , just have to insert entries of SQL based table to room based table, but when we do so both the databases should be initialized but the migration logic of our room database runs before the SQL database is initialized so it gives a runtime error.

@Saifuddin53
Copy link
Contributor Author

@psh can you please help me here ? context: I have successfully migrated to room database for bookmark locations but am not able to find a way to insert all the entries of SQL based bookmark locations table to our new room table

@psh
Copy link
Collaborator

psh commented Feb 4, 2025

The old content provider stored data in a table called bookmarksLocations but your new Room entity is declared with a different name -

@Entity(tableName = "bookmarks_locations")

Try using the old name, and lining up the correct data types from the old DAO and Room should just read the existing data as-is without needing a migration (assuming that Room and the content provider are talking to the same DB file).

You can actually unit-test this to ensure the migration goes well - in the test create and populate a database using test code that looks like the old Java DAO. Then, point your new Room entity at that database and verify with assertions that the data is read correctly.

@Saifuddin53
Copy link
Contributor Author

But @psh we do not have the same DB file for our room and sqlite based database

This commit migrates the bookmark location logic to Kotlin, enhancing code maintainability and readability.

-   Removes the `BookmarkLocationsContentProvider`, `BookmarkLocationsController`, and `BookmarkLocationsDao` Java classes.
-   Creates `BookmarkLocationsDao.kt` and `BookmarkLocationsContentProvider.kt` in Kotlin.
-   Migrates the logic from `BookmarkLocationsFragment.java` to `BookmarkLocationsFragment.kt`.
-   Updates test files to reflect these changes.
-   Addresses associated code review comments.
This commit migrates the bookmark locations functionality from a custom content provider to Room database.

Key changes:

*   **Removal of `BookmarkLocationsContentProvider`:** This class, which previously handled data storage, has been removed.
*   **Introduction of `BookmarksLocations`:** This data class now represents a bookmarked location, serving as the Room entity.
*   **Creation of `BookmarkLocationsDao`:** This Room DAO handles database interactions for bookmark locations, including:
    *   Adding, deleting, and querying bookmarked locations.
    *   Checking if a location is already bookmarked.
    *   Updating the bookmark status of a location.
    *   Retrieving all bookmarked locations as `Place` objects.
*   **`BookmarkLocationsViewModel`:** Added to manage the data layer for bookmark locations
*   **`NearbyUtil`:** Created a Util class for Nearby to manage the bookmark locations.
*   **Updates in `PlaceAdapter` and `PlaceAdapterDelegate`:** These classes have been modified to work with the new Room-based data layer.
*   **Updates in `AppDatabase`:** The database now includes `BookmarksLocations` as an entity and exposes the `bookmarkLocationsDao`.
*   **Updates in `FragmentBuilderModule` and `CommonsApplicationModule`**: for DI
*   **Removal of `DBOpenHelper` upgrade for locations**: as it is no longer needed
* **Updates in `NearbyParentFragmentPresenter`**: refactored the logic to use the dao functions
* **Updates in `NearbyParentFragment`**: refactored the logic to use the util and dao functions
* **Update in `BookmarkLocationsController`**: removed as its no longer needed
* **Add `toPlace` and `toBookmarksLocations`**: extension functions to map between data class and entities
* **Update in `CommonsApplication`**: to remove old db table.
This commit includes the following changes:

-   Updates the database version to 20.
-   Refactors bookmark location handling within `NearbyParentFragment` to improve logic and efficiency.
-   Introduces `getBookmarkLocationExists` in `NearbyUtil` to directly update bookmark icons in the bottom sheet adapter.
-   Removes unused provider.
-   Adjusts `BookmarkLocationsFragment`'s `PlaceAdapter` to use `lifecycleScope` for better coroutine management.
-   Refactors `updateBookmarkLocation` in `NearbyParentFragmentPresenter`.
…ation each time in the bookmark

* Update bookmark button image in `BottomSheetAdapter`
* Add new toggle function to `BottomSheetAdapter`
* Call the toggle function in `NearbyParentFragment`
*   `BookmarkLocationsController`: Changed to use `Flow` to load bookmarked locations.
*   `BookmarkLocationsDao`: Changed to return `Flow` of bookmark location instead of a list.
*   `BookmarkLocationsFragment`: Used `LifecycleScope` to collect data from flow and display it.
*   Removed unused `getAllBookmarkLocations()` from `BookmarkLocationsViewModel`.
*   Used `map` in `BookmarkLocationsDao` to convert from `BookmarksLocations` to `Place` list.
*   BookmarkLocationsController: Changed `loadFavoritesLocations` to be a suspend function.
*   BookmarkLocationsFragment: Updated the `initList` function to call the controller's suspend function.
*   BookmarkLocationsDao: Changed `getAllBookmarksLocations` and `getAllBookmarksLocationsPlace` to be suspend functions.
…utines

*  Migrated `bookmarkDao!!.getAllBookmarksLocations()` to `bookmarkDao!!.getAllBookmarksLocationsPlace()` and added `runBlocking`
*  Added `runBlocking` for `loadBookmarkedLocations()` and `testInitNonEmpty()`
*  Added `runBlocking` for `getAllBookmarksLocations()` and update `updateMapMarkers`

These changes improve the test structure by ensuring the database functions are executed within a coroutine context.
…sDao

*   Moved `initList` to `BookmarkLocationsFragment` for better lifecycle handling.
*   Added test case for `BookmarkLocationsFragment`'s onResume.
*   Added test cases for `BookmarkLocationsDao` operations like adding, retrieving, finding, deleting and updating bookmarks.
…s not null

* BookmarkLocationsFragment: load favorites locations only when view is not null.
* BookmarkLocationsFragmentTest: added spy and verify to test onResume() call initList() method.
*   `AppDatabase`: Updated to use room migration.
*   `CommonsApplicationModule`: added migration from version 19 to 20 to `appDatabase`
…ark_enhancement

# Conflicts:
#	app/src/main/java/fr/free/nrw/commons/nearby/fragments/NearbyParentFragment.java
…rtion

*   Modify the database migration from version 19 to 20 to properly handle null values and use direct data insertion.
*   Create a new table `bookmarks_locations` with `NOT NULL` constraints.
*   Directly insert data into the new table from old database, safely handling `NULL` values by using `DEFAULT` empty string value.
*   Close old database cursor and the database connection.
* Drop the old `bookmarksLocations` table.
…rate data

*   Delete `bookmarksLocations` table from the database during schema upgrade
*   Migrate data from `bookmarksLocations` to `bookmarks_locations` in new schema
*   Add `INSERT OR REPLACE` to prevent duplication during migration
*   Delete `CONTRIBUTIONS_TABLE` and `BOOKMARKS_LOCATIONS` in the application start-up to have a fresh DB for first start-up after update
@Saifuddin53
Copy link
Contributor Author

@nicolas-raoul @psh ready for review

@Saifuddin53 Saifuddin53 marked this pull request as ready for review February 23, 2025 08:24
@nicolas-raoul
Copy link
Member

nicolas-raoul commented Feb 23, 2025

After using the app on main branch then installing this branch, I get the crash below.
Is it expected?
Not sure why it mentions beta, I am using prod.
After deleting the app's data it seems to work fine, though.

java.lang.RuntimeException: Unable to start activity ComponentInfo{fr.free.nrw.commons/fr.free.nrw.commons.contributions.MainActivity}: android.database.sqlite.SQLiteCantOpenDatabaseException: Cannot open database [unable to open database file (code 14 SQLITE_CANTOPEN): No such file or directory] '/data/user/0/fr.free.nrw.commons.beta/databases/commons.db' with flags 0x1: Directory /data/user/0/fr.free.nrw.commons.beta/databases doesn't exist
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:4206)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:4393)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:222)
at android.app.servertransaction.TransactionExecutor.executeNonLifecycleItem(TransactionExecutor.java:133)
at android.app.servertransaction.TransactionExecutor.executeTransactionItems(TransactionExecutor.java:103)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:80)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2773)
at android.os.Handler.dispatchMessage(Handler.java:109)
at android.os.Looper.loopOnce(Looper.java:232)
at android.os.Looper.loop(Looper.java:317)
at android.app.ActivityThread.main(ActivityThread.java:8934)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:591)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:911)
Caused by: android.database.sqlite.SQLiteCantOpenDatabaseException: Cannot open database [unable to open database file (code 14 SQLITE_CANTOPEN): No such file or directory] '/data/user/0/fr.free.nrw.commons.beta/databases/commons.db' with flags 0x1: Directory /data/user/0/fr.free.nrw.commons.beta/databases doesn't exist
at android.database.sqlite.SQLiteConnection.open(SQLiteConnection.java:276)
at android.database.sqlite.SQLiteConnection.open(SQLiteConnection.java:220)
at android.database.sqlite.SQLiteConnectionPool.openConnectionLocked(SQLiteConnectionPool.java:540)
at android.database.sqlite.SQLiteConnectionPool.open(SQLiteConnectionPool.java:226)
at android.database.sqlite.SQLiteConnectionPool.open(SQLiteConnectionPool.java:218)
at android.database.sqlite.SQLiteDatabase.openInner(SQLiteDatabase.java:1191)
at android.database.sqlite.SQLiteDatabase.open(SQLiteDatabase.java:1171)
at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:1062)
at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:1009)
at fr.free.nrw.commons.di.CommonsApplicationModule$Companion$MIGRATION_19_TO_20$1.migrate(CommonsApplicationModule.kt:280)
at androidx.room.RoomOpenHelper.onUpgrade(RoomOpenHelper.kt:90)
at androidx.sqlite.db.framework.FrameworkSQLiteOpenHelper$OpenHelper.onUpgrade(FrameworkSQLiteOpenHelper.kt:253)
at android.database.sqlite.SQLiteOpenHelper.getDatabaseLocked(SQLiteOpenHelper.java:415)
at android.database.sqlite.SQLiteOpenHelper.getWritableDatabase(SQLiteOpenHelper.java:316)
at androidx.sqlite.db.framework.FrameworkSQLiteOpenHelper$OpenHelper.getWritableOrReadableDatabase(FrameworkSQLiteOpenHelper.kt:232)
at androidx.sqlite.db.framework.FrameworkSQLiteOpenHelper$OpenHelper.innerGetDatabase(FrameworkSQLiteOpenHelper.kt:190)
at androidx.sqlite.db.framework.FrameworkSQLiteOpenHelper$OpenHelper.getSupportDatabase(FrameworkSQLiteOpenHelper.kt:151)
at androidx.sqlite.db.framework.FrameworkSQLiteOpenHelper.getWritableDatabase(FrameworkSQLiteOpenHelper.kt:104)
at androidx.room.RoomDatabase.inTransaction(RoomDatabase.kt:632)
at androidx.room.RoomDatabase.assertNotSuspendingTransaction(RoomDatabase.kt:451)
at androidx.room.RoomDatabase.query(RoomDatabase.kt:480)
at androidx.room.util.DBUtil.query(DBUtil.kt:75)
at fr.free.nrw.commons.contributions.ContributionDao_Impl$7.call(ContributionDao_Impl.java:1515)
at fr.free.nrw.commons.contributions.ContributionDao_Impl$7.call(ContributionDao_Impl.java:1511)
at androidx.room.RxRoom$5.subscribe(RxRoom.java:227)
at io.reactivex.internal.operators.single.SingleCreate.subscribeActual(SingleCreate.java:39)
at io.reactivex.Single.subscribe(Single.java:3603)
at io.reactivex.internal.operators.single.SingleSubscribeOn$SubscribeOnObserver.run(SingleSubscribeOn.java:89)
at io.reactivex.Scheduler$DisposeTask.run(Scheduler.java:578)
at io.reactivex.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:66)
at io.reactivex.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:57)
at java.util.concurrent.FutureTask.run(FutureTask.java:264)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:307)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:644)
at java.lang.Thread.run(Thread.java:1012)
Caused by: android.database.sqlite.SQLiteCantOpenDatabaseException: unable to open database file (code 14 SQLITE_CANTOPEN): No such file or directory
at android.database.sqlite.SQLiteConnection.nativeOpen(Native Method)
at android.database.sqlite.SQLiteConnection.open(SQLiteConnection.java:239)
... 35 more

@Saifuddin53
Copy link
Contributor Author

@nicolas-raoul can you please try it again, I have made the necessary changes!

Copy link
Member

@nicolas-raoul nicolas-raoul left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not crashing anymore when migrating from main.
Bookmarks kept successfully.
Working as expected, in particular adding place bookmark works.
Thanks a lot! :-)

@nicolas-raoul nicolas-raoul merged commit 1c7dce9 into commons-app:main Feb 24, 2025
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants