Skip to content

Conversation

@Redd87
Copy link
Collaborator

@Redd87 Redd87 commented Oct 30, 2024

This PR implements the search backend of the project using Jetpack's AppSearch.

There is a local search database in the SearchRepository which is created on startup based on the data from the Association- and EventRepository, and that database can add, remove documents, and most importantly be queried. The ranking strategy - the order in which matching results will show - is currently set to none; as long as there is a matching keyword the result will show. This can later be modified to promote or hide certain documents.
In the Event and Association classes, I created an annex object EventDocument and AssociationDocument, which consists of the attributes on which to search.
The SearchViewModel exposes the query function of the SearchRepository, and allows access to the result data in its StateFlow.
The SearchRepository is accompanied with a SearchRepositoryTest file containing Unit tests, but the SearchViewModel is not. It will need to have integrations test as unit tests do not make sense in view of the file. This will be done at a later time as this PR is already quite large.
The main has been modified to properly set up the search backend, and is currently injected into the explore page.

Redd87 added 30 commits October 17, 2024 10:10
…t/search

# Conflicts:
#	app/src/main/java/com/android/unio/MainActivity.kt
#	gradle/libs.versions.toml
…t/search

# Conflicts:
#	app/src/main/java/com/android/unio/model/firestore/FirestoreReferenceList.kt
Seriously why the fuck can't mockito mock a final class if all classes are final by default
@Redd87 Redd87 linked an issue Oct 30, 2024 that may be closed by this pull request
Copy link
Collaborator

@Zafouche Zafouche left a comment

Choose a reason for hiding this comment

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

Thank you for your amazing work on this difficult task! 🚀

Overall, great code! Good start on the tests as well, since I know you plan on adding more in the next Sprint, no need to comment more than that on the code coverage.

The only thing that bothers me is the choice of architecture for the Search view model and repository. In brief, I think it violates the MVVM pattern, because in my opinion the Search repository shouldn't be able to utilize the AssociationRepository and the EventRepository. I think what would be a correct pattern would be to have the Search view model utilize the AssociationViewModel and the EventViewModel. This would require passing the onSuccess and onFailure callbacks through the function parameters to the Search repository, so we would have the classic ViewModel-Repository relationship we have in all other such pairs.
Have a look at my comments on the respective files. Please explain to me your choice in those, and I will gladly approve this PR!

Other than that, you'll find my comments to be on small details which aren't very important. However don't forget to remove the debugging Log.d calls, and optimize the imports!

fun testExploreDisplayed() {
composeTestRule.setContent { ExploreScreen(navigationAction) }
composeTestRule.setContent {
ExploreScreen(navigationAction, searchViewModel = searchViewModel)
Copy link
Collaborator

Choose a reason for hiding this comment

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

I know it's just in the test file, but either parameter = value (e.g. here it's searchViewModel = searchViewModel) should be used for all parameters or none at all, not both

val associationRepository = AssociationRepositoryFirestore(Firebase.firestore)
val associationViewModel = AssociationViewModel(associationRepository)
val associationRepository = remember { AssociationRepositoryFirestore(db) }
val associationViewModel = remember { AssociationViewModel(associationRepository) }
Copy link
Collaborator

Choose a reason for hiding this comment

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

Very nice! Thanks for this, as this was forgotten, it caused screen flickering when the UnioApp composable, which is not pleasant both to the eye but also in terms of useless re-computation!

val fullName: String = "",
@StringProperty(indexingType = StringPropertyConfig.INDEXING_TYPE_PREFIXES)
val description: String = ""
// TODO add members
Copy link
Collaborator

Choose a reason for hiding this comment

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

Well defined. I assume these are the properties that can be searchable? The most important are name and fullName, which are there, but can you enlighten me on if these are all searchable properties? If so, I don't think searching a description is really useful.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes these are all searchable, and I disagree, as I feel like you might not sometimes remember the association name but want can then find it with keywords that are in the description

@@ -1,5 +1,7 @@
package com.android.unio.model.association

import com.google.firebase.firestore.QuerySnapshot
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is this an unnecessary import? I don't see any other additions in the file that would justify this new import.

import com.google.firebase.Firebase
import com.google.firebase.auth.auth
import com.google.firebase.firestore.FirebaseFirestore
import com.google.firebase.firestore.QuerySnapshot
Copy link
Collaborator

Choose a reason for hiding this comment

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

Again an other unnecessary import. Maybe you forgot to run optimize imports

*/
// Should be private, but I need it public for tests
fun addAssociations(associations: List<Association>) {
Log.d("SearchRepository", "Adding associations to search database")
Copy link
Collaborator

Choose a reason for hiding this comment

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

Don't forget to remove this debug Log!

*/
// Should be private, but I need it public for tests
fun addEvents(events: List<Event>) {
Log.d("SearchRepository", "Adding events to search database")
Copy link
Collaborator

Choose a reason for hiding this comment

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

Another debug Log!

Firebase.auth.addAuthStateListener {
if (it.currentUser != null) {
viewModelScope.launch { repository.init() }
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Have a look at how the init block is defined in AssociationViewModel:

init {
    associationRepository.init { getAssociations() }
  }

It simply calls the repository's init function. You'll see that the addition of the Auth State Listener is done in that repository init function, e.g. in AssociationRepositoryFirestore:

override fun init(onSuccess: () -> Unit) {
    Firebase.auth.addAuthStateListener {
      if (it.currentUser != null) {
        onSuccess()
      }
    }
  }

I think it makes more sense to add the Auth State Listener there as it is the database's duty, but let me know if you disagree

Copy link
Collaborator

Choose a reason for hiding this comment

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

Strong tests! 💪

mockitoAndroid = "5.13.0"
mockkMockk = "1.9.3"
mockkVersion = "1.13.0"
mockkVersion = "1.13.7"
Copy link
Collaborator

Choose a reason for hiding this comment

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

Good call on upgrading the version, I see nothing breaks!

@Redd87
Copy link
Collaborator Author

Redd87 commented Oct 31, 2024

Thank you for your amazing work on this difficult task! 🚀

Overall, great code! Good start on the tests as well, since I know you plan on adding more in the next Sprint, no need to comment on the code coverage.

The only thing that bothers me is the choice of architecture for the Search view model and repository. In brief, I think it violates the MVVM pattern, because in my opinion the Search repository shouldn't be able to utilize the AssociationRepository and the EventRepository. I think what would be a correct pattern would be to have the Search view model utilize the AssociationViewModel and the EventViewModel. This would require passing the onSuccess and onFailure callbacks through the function parameters to the Search repository, so we would have the classic ViewModel-Repository relationship we have in all other such pairs. Have a look at my comments on the respective files. Please explain to me your choice in those, and I will gladly approve this PR!

Other than that, you'll find my comments to be on small details which aren't very important. However don't forget to remove the debugging Log.d calls, and optimize the imports!

Thanks for the feedback, I'll do these changes ASAP. The justification behind the SearchRepository is that it acts as a composite repository for the Association and EventRepository; however I honestly simply didn't consider doing it as you suggested. I don't feel that the SearchRepository violates the MVVM model, as it receives the other repositories by dependency injection and simply aggregates their data, there is no interaction between the repositories, but I do agree that the SearchViewModel is quite different to the other ViewModels, but I don't know if that itself is a violation of the MVVM. I could refactor it to adhere to the others but that would take quite a lot of time and I'm not sure if it's that advantageous

Let me know if you have other questions

# Conflicts:
#	app/src/androidTest/java/com/android/unio/ui/ScreenDisplayingTest.kt
#	app/src/androidTest/java/com/android/unio/ui/explore/ExploreScreenTest.kt
#	app/src/main/java/com/android/unio/MainActivity.kt
val associationRepository = AssociationRepositoryFirestore(Firebase.firestore)
val associationViewModel = AssociationViewModel(associationRepository)
val associationRepository = remember { AssociationRepositoryFirestore(db) }
val associationViewModel = remember { AssociationViewModel(associationRepository) }
Copy link
Collaborator

Choose a reason for hiding this comment

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

Good change. This will be fixed with Hilt later though. Same for the ones below

}

private fun <T> performFirestoreOperation(
task: Task<T>,
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is a duplicate of AssociationRepositoryFirestore.performFirestoreOperation(...). We should consider refactor this to put them in a separate file. This should be done in another task/PR though

*/
// Should be private, but I need it public for tests
fun addAssociations(associations: List<Association>) {
Log.d("SearchRepository", "Adding associations to search database")
Copy link
Collaborator

Choose a reason for hiding this comment

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

I guess this is a resual testing mechanism. This should be removed in my opinion.

// Should be private, but I need it public for tests
fun addEvents(events: List<Event>) {
Log.d("SearchRepository", "Adding events to search database")
val eventDocuments = events.map { it.toEventDocument() }
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same, testing mechanism to be removed

* Factory for creating a [SearchViewModel] with a constructor that takes a [SearchRepository].
*/
companion object {
fun provideFactory(
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is the factory really useful here ? We will remove Factories anyway later so, maybe we can remove it now

…t/search

# Conflicts:
#	app/src/main/java/com/android/unio/MainActivity.kt
#	app/src/main/java/com/android/unio/model/event/EventRepositoryFirestore.kt
@sonarqubecloud
Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
55.9% Coverage on New Code (required ≥ 80%)

See analysis details on SonarCloud

@Redd87 Redd87 merged commit ee39706 into main Oct 31, 2024
1 of 2 checks passed
@Redd87 Redd87 deleted the feat/search branch October 31, 2024 17:42
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.

Create backend of search feature

5 participants