Skip to content

Commit

Permalink
Coroutine Dispatcher Anti Pattern Fixed, Firebase Crashlytics added
Browse files Browse the repository at this point in the history
  • Loading branch information
vaibhav committed Mar 27, 2022
1 parent e5e67ad commit 4531610
Show file tree
Hide file tree
Showing 14 changed files with 101 additions and 53 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@
.cxx
local.properties

/keystore.properties
2 changes: 1 addition & 1 deletion .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 15 additions & 8 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,25 @@ plugins {
id 'dagger.hilt.android.plugin'
id 'androidx.navigation.safeargs'
id "com.apollographql.apollo"
id 'com.google.firebase.crashlytics'
}

android {
def propsFile = rootProject.file('keystore.properties')
def props = new Properties()
props.load(new FileInputStream(propsFile))
signingConfigs {
debug {
storeFile file('E:\\AndroidStudioProjects\\keystore\\quizzify.jks')
storePassword 'Vaibhav3011'
keyAlias 'quizzify'
keyPassword 'Vaibhav3011'
storeFile = file(props['storeFile'])
storePassword = props['storePassword']
keyAlias = props['keyAlias']
keyPassword = props['keyPassword']
}
release {
storeFile file('E:\\AndroidStudioProjects\\keystore\\quizzify.jks')
keyAlias 'quizzify'
storePassword 'Vaibhav3011'
keyPassword 'Vaibhav3011'
storeFile = file(props['storeFile'])
keyAlias = props['storePassword']
storePassword = props['keyAlias']
keyPassword = props['keyPassword']
}
}
compileSdk 31
Expand Down Expand Up @@ -55,6 +59,7 @@ android {

}


dependencies {

implementation 'androidx.core:core-ktx:1.7.0'
Expand All @@ -76,6 +81,8 @@ dependencies {
implementation platform('com.google.firebase:firebase-bom:29.0.3')
implementation 'com.google.firebase:firebase-firestore-ktx'
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.6.0"
implementation 'com.google.firebase:firebase-crashlytics'
implementation 'com.google.firebase:firebase-analytics'

//hilt
implementation "com.google.dagger:hilt-android:$hilt_version"
Expand Down
Binary file modified app/release/app-release.aab
Binary file not shown.
Binary file modified app/release/app-release.apk
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,27 @@ package dev.vaibhav.quizzify.data.local
import dev.vaibhav.quizzify.data.local.room.QuizzifyDao
import dev.vaibhav.quizzify.data.models.remote.CategoryDto
import dev.vaibhav.quizzify.data.models.remote.QuizDto
import dev.vaibhav.quizzify.util.DispatcherProvider
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import javax.inject.Inject

class QuizLocalDataSourceImpl @Inject constructor(private val dao: QuizzifyDao) :
class QuizLocalDataSourceImpl @Inject constructor(
private val dao: QuizzifyDao,
private val dispatchers: DispatcherProvider
) :
QuizLocalDataSource {
override fun getAllQuizzes(query: String, category: CategoryDto?) = dao.getAllQuizzes()
.map { quizzes ->
val list = quizzes.filter {
it.name.startsWith(query, ignoreCase = true)
}
category?.let { list.filter { it.category.id == category.id } } ?: list
}
}.flowOn(dispatchers.io)

override suspend fun insertQuizzes(quizzes: List<QuizDto>) = dao.insertQuizzes(quizzes)

override fun getAllCategories() = dao.getAllCategories()
override fun getAllCategories() = dao.getAllCategories().flowOn(dispatchers.io)

override suspend fun insertCategories(categories: List<CategoryDto>) =
dao.insertCategories(categories)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,19 @@ import dev.vaibhav.quizzify.data.local.dataStore.LocalDataStore
import dev.vaibhav.quizzify.data.models.remote.UserDto
import dev.vaibhav.quizzify.data.remote.auth.AuthDataSource
import dev.vaibhav.quizzify.data.remote.user.UserDataSource
import dev.vaibhav.quizzify.util.*
import dev.vaibhav.quizzify.util.Constants.avatars
import dev.vaibhav.quizzify.util.DATA_NULL
import dev.vaibhav.quizzify.util.Resource
import dev.vaibhav.quizzify.util.mapMessages
import dev.vaibhav.quizzify.util.mapToUnit
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import javax.inject.Inject

class AuthRepositoryImpl @Inject constructor(
private val authDataSource: AuthDataSource,
private val userDataSource: UserDataSource,
private val dataStore: LocalDataStore
private val dataStore: LocalDataStore,
private val dispatchers: DispatcherProvider
) : AuthRepository {
override val currentUserId: String
get() = authDataSource.userId
Expand All @@ -46,7 +45,7 @@ class AuthRepositoryImpl @Inject constructor(
emit(resource.mapToUnit())
}.map {
it.mapMessages("Successfully logged in user", errorMessage = "Failed to login User")
}
}.flowOn(dispatchers.io)

override suspend fun registerUser(
username: String,
Expand All @@ -60,7 +59,7 @@ class AuthRepositoryImpl @Inject constructor(
if (res is Resource.Success) registerResource else res
} else registerResource
emit(resource.mapToUnit())
}
}.flowOn(dispatchers.io)

override suspend fun loginUsingGoogle(data: Intent): Flow<Resource<Unit>> = flow {
emit(Resource.Loading())
Expand All @@ -73,7 +72,7 @@ class AuthRepositoryImpl @Inject constructor(
val signInResource = authDataSource.signInUsingCredential(credential)
val resource = handleAfterGoogleLogin(signInResource)
emit(resource)
}
}.flowOn(dispatchers.io)

private suspend fun handleAfterGoogleLogin(resource: Resource<FirebaseUser>): Resource<Unit> {
return if (resource is Resource.Success) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,30 @@ import dev.vaibhav.quizzify.data.models.remote.game.Game
import dev.vaibhav.quizzify.data.models.remote.game.GameState
import dev.vaibhav.quizzify.data.models.remote.game.Player
import dev.vaibhav.quizzify.data.remote.game.GameDataSource
import dev.vaibhav.quizzify.util.DATA_NULL
import dev.vaibhav.quizzify.util.Resource
import dev.vaibhav.quizzify.util.mapMessages
import dev.vaibhav.quizzify.util.mapTo
import kotlinx.coroutines.Dispatchers
import dev.vaibhav.quizzify.util.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import javax.inject.Inject

class GameRepoImpl @Inject constructor(private val gameDataSource: GameDataSource) : GameRepo {
class GameRepoImpl @Inject constructor(
private val gameDataSource: GameDataSource,
private val dispatchers: DispatcherProvider
) : GameRepo {

override suspend fun addGame(game: Game): Flow<Resource<Unit>> = flow {
emit(Resource.Loading())
emit(gameDataSource.upsertGame(game))
}.map { it.mapMessages("Created Game", "Failed to create game") }
.flowOn(Dispatchers.IO)
.flowOn(dispatchers.io)

override suspend fun connectToGame(gameId: String, player: Player): Flow<Resource<Unit>> =
flow {
emit(Resource.Loading())
emit(gameDataSource.connectToGame(gameId, player))
}.map { it.mapMessages("Connected to Game", "Failed to connect to game") }
.flowOn(Dispatchers.IO)
.flowOn(dispatchers.io)

override suspend fun observeGame(gameId: String): Flow<Resource<Game>> =
gameDataSource.observeGame(gameId)
Expand All @@ -37,7 +36,7 @@ class GameRepoImpl @Inject constructor(private val gameDataSource: GameDataSourc
emit(Resource.Loading())
emit(gameDataSource.updateGameState(gameId, GameState.STARTED.toString()))
}.map { it.mapMessages("Game Started", "Failed to start game") }
.flowOn(Dispatchers.IO)
.flowOn(dispatchers.io)

override suspend fun doesGameExist(gameId: String): Flow<Resource<Boolean>> = flow {
emit(Resource.Loading())
Expand Down Expand Up @@ -66,14 +65,14 @@ class GameRepoImpl @Inject constructor(private val gameDataSource: GameDataSourc
val newGame = game.copy(players = newPlayerList)
emit(gameDataSource.upsertGame(newGame))
}.map { it.mapMessages("Successfully left Game", "Failed to leave game") }
.flowOn(Dispatchers.IO)
.flowOn(dispatchers.io)

override suspend fun cancelGame(gameId: String): Flow<Resource<Unit>> = flow {
emit(Resource.Loading())
emit(gameDataSource.updateGameState(gameId, GameState.CANCELED.name))
}.map {
it.mapMessages("Canceled Game", "Failed to cancel game")
}.flowOn(Dispatchers.IO)
}.flowOn(dispatchers.io)

override suspend fun completeGame(game: Game, player: Player): Flow<Resource<Unit>> = flow {
emit(Resource.Loading())
Expand All @@ -88,7 +87,7 @@ class GameRepoImpl @Inject constructor(private val gameDataSource: GameDataSourc
)
emit(gameDataSource.upsertGame(newGame))
}.map { it.mapMessages("Game finished", "Failed to finish Game") }
.flowOn(Dispatchers.IO)
.flowOn(dispatchers.io)

override suspend fun submitAnswer(game: Game, player: Player): Flow<Resource<Unit>> = flow {
emit(Resource.Loading())
Expand All @@ -100,7 +99,7 @@ class GameRepoImpl @Inject constructor(private val gameDataSource: GameDataSourc
val newGame = game.copy(players = newPlayerList)
emit(gameDataSource.upsertGame(newGame))
}.map { it.mapMessages("Answer submitted", "Failed to submit answer") }
.flowOn(Dispatchers.IO)
.flowOn(dispatchers.io)

override suspend fun getAllGamesOfUser(userId: String): Flow<Resource<List<Game>>> = flow {
emit(Resource.Loading())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,7 @@ import dev.vaibhav.quizzify.data.models.remote.CategoryDto
import dev.vaibhav.quizzify.data.models.remote.QuizDto
import dev.vaibhav.quizzify.data.remote.quiz.InstantQuizDataSource
import dev.vaibhav.quizzify.data.remote.quiz.QuizRemoteDataSource
import dev.vaibhav.quizzify.util.Resource
import dev.vaibhav.quizzify.util.mapMessages
import dev.vaibhav.quizzify.util.mapTo
import dev.vaibhav.quizzify.util.mapToUnit
import kotlinx.coroutines.Dispatchers
import dev.vaibhav.quizzify.util.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
Expand All @@ -21,38 +17,39 @@ import javax.inject.Inject
class QuizRepoImpl @Inject constructor(
private val quizLocalDataSource: QuizLocalDataSource,
private val quizRemoteDataSource: QuizRemoteDataSource,
private val instantQuizDataSource: InstantQuizDataSource
private val instantQuizDataSource: InstantQuizDataSource,
private val dispatchers: DispatcherProvider
) : QuizRepo {

override val allCategories: Flow<List<CategoryDto>>
get() = quizLocalDataSource.getAllCategories().flowOn(Dispatchers.IO)
get() = quizLocalDataSource.getAllCategories().flowOn(dispatchers.io)

override suspend fun getAllQuizzes(
query: String,
categoryDto: CategoryDto?
): Flow<List<QuizDto>> =
quizLocalDataSource.getAllQuizzes(query, categoryDto).flowOn(Dispatchers.IO)
quizLocalDataSource.getAllQuizzes(query, categoryDto).flowOn(dispatchers.io)

override suspend fun getAllFavouriteQuizzes(favourites: List<String>) =
quizLocalDataSource.getAllQuizzes()
.map { it.filter { quiz -> quiz.id in favourites } }
.flowOn(Dispatchers.IO)
.flowOn(dispatchers.io)

override suspend fun fetchAllCategories(): Flow<Resource<Unit>> = flow {
emit(Resource.Loading())
val resource = quizRemoteDataSource.getAllCategories()
if (resource is Resource.Success)
quizLocalDataSource.insertCategories(resource.data!!)
emit(resource.mapToUnit())
}.flowOn(Dispatchers.IO)
}.flowOn(dispatchers.io)

override suspend fun fetchAllQuizzes(): Flow<Resource<Unit>> = flow {
emit(Resource.Loading())
val resource = quizRemoteDataSource.getAllQuizzes()
if (resource is Resource.Success)
quizLocalDataSource.insertQuizzes(resource.data!!.shuffled())
emit(resource.mapToUnit())
}.flowOn(Dispatchers.IO)
}.flowOn(dispatchers.io)

override suspend fun fetchInstantQuiz(
count: Int,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ package dev.vaibhav.quizzify.data.repo.user
import dev.vaibhav.quizzify.data.local.dataStore.LocalDataStore
import dev.vaibhav.quizzify.data.models.remote.UserDto
import dev.vaibhav.quizzify.data.remote.user.UserDataSource
import dev.vaibhav.quizzify.util.DispatcherProvider
import dev.vaibhav.quizzify.util.Resource
import dev.vaibhav.quizzify.util.mapMessages
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
Expand All @@ -14,33 +14,34 @@ import javax.inject.Inject

class UserRepoImpl @Inject constructor(
private val userDataSource: UserDataSource,
private val localDataStore: LocalDataStore
private val localDataStore: LocalDataStore,
private val dispatchers: DispatcherProvider
) : UserRepo {

override fun observeCurrentUser() = localDataStore.getUserDataFlow().flowOn(Dispatchers.IO)
override fun observeCurrentUser() = localDataStore.getUserDataFlow().flowOn(dispatchers.io)

override suspend fun getCurrentUser() = localDataStore.getUserData()

override suspend fun fetchUserData(userId: String): Flow<Resource<UserDto>> = flow {
emit(Resource.Loading())
emit(userDataSource.getUserData(userId))
}.flowOn(Dispatchers.IO)
}.flowOn(dispatchers.io)

override suspend fun saveUserData(userDto: UserDto): Flow<Resource<Unit>> = flow {
emit(Resource.Loading())
val resource = userDataSource.saveUserData(userDto)
if (resource is Resource.Success)
localDataStore.saveUserData(userDto)
emit(resource)
}.flowOn(Dispatchers.IO)
}.flowOn(dispatchers.io)

override suspend fun updateAvatar(avatar: String): Flow<Resource<Unit>> = flow {
val newUser = getCurrentUser().copy(profilePic = avatar)
emit(Resource.Loading())
val resource = userDataSource.updateAvatar(newUser.userId, avatar)
if (resource is Resource.Success) localDataStore.saveUserData(newUser)
emit(resource)
}.map { it.mapMessages("Updated Avatar", "Failed to update Avatar") }.flowOn(Dispatchers.IO)
}.map { it.mapMessages("Updated Avatar", "Failed to update Avatar") }.flowOn(dispatchers.io)

override suspend fun addFavourite(quizId: String): Flow<Resource<Unit>> = flow {
emit(Resource.Loading())
Expand All @@ -49,7 +50,7 @@ class UserRepoImpl @Inject constructor(
val newUser = user.copy(favourites = newFavourites.toList())
emit(updateFavourites(newUser))
}.map { it.mapMessages("Added to favourites", "Failed to add to favourites") }
.flowOn(Dispatchers.IO)
.flowOn(dispatchers.io)

override suspend fun removeFavourite(quizId: String): Flow<Resource<Unit>> = flow {
emit(Resource.Loading())
Expand All @@ -58,7 +59,7 @@ class UserRepoImpl @Inject constructor(
val newUser = user.copy(favourites = newFavourites.toList())
emit(updateFavourites(newUser))
}.map { it.mapMessages("Removed from favourites", "Failed to remove from favourites") }
.flowOn(Dispatchers.IO)
.flowOn(dispatchers.io)

private suspend fun updateFavourites(user: UserDto): Resource<Unit> {
val resource = userDataSource.updateUserFavourites(user.userId, user.favourites)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import dev.vaibhav.quizzify.data.repo.user.UserRepoImpl

@Module
@InstallIn(SingletonComponent::class)
abstract class InterfaceModules {
abstract class AuthAndUserInterfaceModules {

@Binds
abstract fun bindsLocalDataStore(
Expand Down
18 changes: 18 additions & 0 deletions app/src/main/java/dev/vaibhav/quizzify/di/CoroutinesModule.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package dev.vaibhav.quizzify.di

import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import dev.vaibhav.quizzify.util.AppDispatcherProvider
import dev.vaibhav.quizzify.util.DispatcherProvider

@Module
@InstallIn(SingletonComponent::class)
abstract class CoroutinesModule {

@Binds
abstract fun bindsDispatcherProvider(
appDispatcherProvider: AppDispatcherProvider
): DispatcherProvider
}
Loading

0 comments on commit 4531610

Please sign in to comment.