From f57d575071de4403c58ac7fdfd11d9e9f4919550 Mon Sep 17 00:00:00 2001 From: razzanYoni <13521087@mahasiswa.itb.ac.id> Date: Fri, 22 Mar 2024 05:21:18 +0700 Subject: [PATCH] refactor dependency injection --- app/build.gradle.kts | 5 ++ .../com/informatika/bondoman/MainActivity.kt | 9 ++- .../bondoman/di/ApplicationModule.kt | 17 +++++ .../informatika/bondoman/di/DatabaseModule.kt | 8 +-- .../informatika/bondoman/di/NetworkModule.kt | 15 +---- .../bondoman/di/RepositoryModule.kt | 26 ++++++++ .../bondoman/di/ViewModelModule.kt | 22 ++++--- .../model/local/{AppDB.kt => AppDatabase.kt} | 6 +- .../model/local/dao/TransactionDao.kt | 12 ++-- .../model/repository/login/LoginRepository.kt | 8 +++ .../LoginRepositoryImpl.kt} | 7 +-- .../model/repository/token/TokenRepository.kt | 7 +++ .../TokenRepositoryImpl.kt} | 10 +-- .../transaction/TransactionRepository.kt | 27 ++++++++ .../TransactionRepositoryImpl.kt} | 38 ++++++------ .../bondoman/prefdatastore/JWTManager.kt | 62 ++----------------- .../bondoman/prefdatastore/JWTManagerImpl.kt | 50 +++++++++++++++ .../bondoman/view/activity/LoginActivity.kt | 21 +++++-- .../view/activity/MainEmptyActivity.kt | 36 +++++++---- .../view/fragment/TransactionFragment.kt | 14 ++++- .../bondoman/viewmodel/JWTViewModel.kt | 28 +++++++++ .../viewmodel/login/LoginViewModel.kt | 4 +- .../transaction/CreateTransactionViewModel.kt | 2 +- .../transaction/DetailTransactionViewModel.kt | 10 +-- .../transaction/ListTransactionViewModel.kt | 6 +- .../transaction/UpdateTransactionViewModel.kt | 11 ++-- .../informatika/bondoman/CheckAllModules.kt | 28 +++++++++ 27 files changed, 328 insertions(+), 161 deletions(-) create mode 100644 app/src/main/java/com/informatika/bondoman/di/ApplicationModule.kt create mode 100644 app/src/main/java/com/informatika/bondoman/di/RepositoryModule.kt rename app/src/main/java/com/informatika/bondoman/model/local/{AppDB.kt => AppDatabase.kt} (65%) create mode 100644 app/src/main/java/com/informatika/bondoman/model/repository/login/LoginRepository.kt rename app/src/main/java/com/informatika/bondoman/model/repository/{LoginRepository.kt => login/LoginRepositoryImpl.kt} (79%) create mode 100644 app/src/main/java/com/informatika/bondoman/model/repository/token/TokenRepository.kt rename app/src/main/java/com/informatika/bondoman/model/repository/{TokenRepository.kt => token/TokenRepositoryImpl.kt} (68%) create mode 100644 app/src/main/java/com/informatika/bondoman/model/repository/transaction/TransactionRepository.kt rename app/src/main/java/com/informatika/bondoman/model/repository/{TransactionRepository.kt => transaction/TransactionRepositoryImpl.kt} (55%) create mode 100644 app/src/main/java/com/informatika/bondoman/prefdatastore/JWTManagerImpl.kt create mode 100644 app/src/main/java/com/informatika/bondoman/viewmodel/JWTViewModel.kt create mode 100644 app/src/test/java/com/informatika/bondoman/CheckAllModules.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index a687cd2..c74673c 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -88,6 +88,7 @@ dependencies { val koinVersion = "3.5.3" implementation("io.insert-koin:koin-core:$koinVersion") implementation("io.insert-koin:koin-android:$koinVersion") + testImplementation("io.insert-koin:koin-test-junit4:$koinVersion") // Room val roomVersion = "2.6.1" @@ -100,6 +101,10 @@ dependencies { val timberVersion = "5.0.1" implementation("com.jakewharton.timber:timber:$timberVersion") + // mockk + val mockkVersion = "1.13.10" + testImplementation("io.mockk:mockk:$mockkVersion") + testImplementation("junit:junit:4.13.2") androidTestImplementation("androidx.test.ext:junit:1.1.5") androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") diff --git a/app/src/main/java/com/informatika/bondoman/MainActivity.kt b/app/src/main/java/com/informatika/bondoman/MainActivity.kt index bc87cfa..f0ff137 100644 --- a/app/src/main/java/com/informatika/bondoman/MainActivity.kt +++ b/app/src/main/java/com/informatika/bondoman/MainActivity.kt @@ -13,13 +13,15 @@ import com.google.android.material.bottomnavigation.BottomNavigationView import com.informatika.bondoman.databinding.ActivityMainBinding import com.informatika.bondoman.view.activity.LoginActivity import com.informatika.bondoman.prefdatastore.JWTManager +import com.informatika.bondoman.viewmodel.JWTViewModel import kotlinx.coroutines.launch +import org.koin.androidx.viewmodel.ext.android.viewModel class MainActivity : AppCompatActivity() { private lateinit var binding: ActivityMainBinding - private lateinit var jwtManager: JWTManager + private val jwtViewModel: JWTViewModel by viewModel() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -30,8 +32,6 @@ class MainActivity : AppCompatActivity() { binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) - jwtManager = JWTManager(applicationContext) - val navView: BottomNavigationView = binding.navView val navController = findNavController(R.id.nav_host_fragment_activity_main) @@ -54,8 +54,7 @@ class MainActivity : AppCompatActivity() { super.onStart() lifecycleScope.launch { - if (jwtManager.isExpired()) { - jwtManager.onLogout() + if (jwtViewModel.isExpired()) { val intent = Intent(this@MainActivity, LoginActivity::class.java) startActivity(intent) finish() diff --git a/app/src/main/java/com/informatika/bondoman/di/ApplicationModule.kt b/app/src/main/java/com/informatika/bondoman/di/ApplicationModule.kt new file mode 100644 index 0000000..ba6e0f3 --- /dev/null +++ b/app/src/main/java/com/informatika/bondoman/di/ApplicationModule.kt @@ -0,0 +1,17 @@ +package com.informatika.bondoman.di + +import com.informatika.bondoman.prefdatastore.JWTManager +import com.squareup.moshi.Moshi +import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory +import org.koin.android.ext.koin.androidApplication +import org.koin.android.ext.koin.androidContext +import org.koin.dsl.module + +val applicationModule = module { + // Dependency: Moshi + single<Moshi> { + Moshi.Builder() + .add(KotlinJsonAdapterFactory()) + .build() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/informatika/bondoman/di/DatabaseModule.kt b/app/src/main/java/com/informatika/bondoman/di/DatabaseModule.kt index 446f1b9..561ed8f 100644 --- a/app/src/main/java/com/informatika/bondoman/di/DatabaseModule.kt +++ b/app/src/main/java/com/informatika/bondoman/di/DatabaseModule.kt @@ -1,20 +1,20 @@ package com.informatika.bondoman.di import androidx.room.Room -import com.informatika.bondoman.model.local.AppDB +import com.informatika.bondoman.model.local.AppDatabase import com.informatika.bondoman.model.local.DBConstants +import org.koin.android.ext.koin.androidApplication import org.koin.android.ext.koin.androidContext import org.koin.dsl.module val databaseModule = module { // Dependency: AppDB single { - Room.databaseBuilder(androidContext(), AppDB::class.java, DBConstants.mName).build() + Room.databaseBuilder(androidContext(), AppDatabase::class.java, DBConstants.mName).build() } // Dependency: TransactionDao single { - val appDB: AppDB = get() - appDB.transactionDao() + get<AppDatabase>().transactionDao() } } \ No newline at end of file diff --git a/app/src/main/java/com/informatika/bondoman/di/NetworkModule.kt b/app/src/main/java/com/informatika/bondoman/di/NetworkModule.kt index e75873f..09000d5 100644 --- a/app/src/main/java/com/informatika/bondoman/di/NetworkModule.kt +++ b/app/src/main/java/com/informatika/bondoman/di/NetworkModule.kt @@ -3,7 +3,6 @@ package com.informatika.bondoman.di import com.informatika.bondoman.BuildConfig import com.informatika.bondoman.model.remote.AuthService import com.squareup.moshi.Moshi -import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory import okhttp3.Interceptor import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor @@ -31,15 +30,8 @@ val networkModule = module { .build() } - // Dependency: Moshi - single<Moshi> { - Moshi.Builder() - .add(KotlinJsonAdapterFactory()) - .build() - } - // Dependency: Retrofit - single { + single<Retrofit> { Retrofit.Builder() .baseUrl(BuildConfig.BASE_URL) .client(get()) @@ -48,8 +40,5 @@ val networkModule = module { } // Dependency: ApiService - single { - val retrofit: Retrofit = get() - retrofit.create(AuthService::class.java) - } + single<AuthService> {get<Retrofit>().create(AuthService::class.java)} } \ No newline at end of file diff --git a/app/src/main/java/com/informatika/bondoman/di/RepositoryModule.kt b/app/src/main/java/com/informatika/bondoman/di/RepositoryModule.kt new file mode 100644 index 0000000..1b6c40c --- /dev/null +++ b/app/src/main/java/com/informatika/bondoman/di/RepositoryModule.kt @@ -0,0 +1,26 @@ +package com.informatika.bondoman.di + +import com.informatika.bondoman.model.repository.login.LoginRepository +import com.informatika.bondoman.model.repository.login.LoginRepositoryImpl +import com.informatika.bondoman.model.repository.token.TokenRepository +import com.informatika.bondoman.model.repository.token.TokenRepositoryImpl +import com.informatika.bondoman.model.repository.transaction.TransactionRepository +import com.informatika.bondoman.model.repository.transaction.TransactionRepositoryImpl +import org.koin.dsl.module + +val repositoryModule = module { + // Dependency: LoginRepository + single<LoginRepository> { + LoginRepositoryImpl(get()) + } + + single<TokenRepository> { + TokenRepositoryImpl(get()) + } + + // Dependency: TransactionRepository + single<TransactionRepository> { + TransactionRepositoryImpl(get()) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/informatika/bondoman/di/ViewModelModule.kt b/app/src/main/java/com/informatika/bondoman/di/ViewModelModule.kt index 4dd6ff5..acd0370 100644 --- a/app/src/main/java/com/informatika/bondoman/di/ViewModelModule.kt +++ b/app/src/main/java/com/informatika/bondoman/di/ViewModelModule.kt @@ -1,17 +1,26 @@ package com.informatika.bondoman.di -import com.informatika.bondoman.model.repository.LoginRepository +import com.informatika.bondoman.prefdatastore.JWTManager +import com.informatika.bondoman.prefdatastore.JWTManagerImpl +import com.informatika.bondoman.viewmodel.JWTViewModel import org.koin.androidx.viewmodel.dsl.viewModel import com.informatika.bondoman.viewmodel.login.LoginViewModel import com.informatika.bondoman.viewmodel.transaction.CreateTransactionViewModel import com.informatika.bondoman.viewmodel.transaction.DetailTransactionViewModel -import com.informatika.bondoman.viewmodel.transaction.UpdateTransactionViewModel import com.informatika.bondoman.viewmodel.transaction.ListTransactionViewModel +import com.informatika.bondoman.viewmodel.transaction.UpdateTransactionViewModel +import org.koin.android.ext.koin.androidContext +import org.koin.androidx.viewmodel.dsl.viewModelOf import org.koin.dsl.module val viewModelModule = module { - // Dependency: LoginViewModel + // Dependency: JWTViewModel viewModel { + JWTViewModel(JWTManagerImpl(androidContext()), get()) + } + + // Dependency: LoginViewModel + viewModel<LoginViewModel> { LoginViewModel(get()) } @@ -22,12 +31,12 @@ val viewModelModule = module { // Dependency: DetailTransactionViewModel viewModel { - parameters -> DetailTransactionViewModel(get(), transaction = parameters.get()) + parameters -> DetailTransactionViewModel(get(), _id = parameters.get()) } // Dependency: UpdateTransactionViewModel viewModel { - parameters -> UpdateTransactionViewModel(get(), transaction = parameters.get()) + parameters -> UpdateTransactionViewModel(get(), _id = parameters.get()) } // Dependency: ListTransactionViewModel @@ -35,7 +44,4 @@ val viewModelModule = module { ListTransactionViewModel(get()) } - single { - LoginRepository(get()) - } } \ No newline at end of file diff --git a/app/src/main/java/com/informatika/bondoman/model/local/AppDB.kt b/app/src/main/java/com/informatika/bondoman/model/local/AppDatabase.kt similarity index 65% rename from app/src/main/java/com/informatika/bondoman/model/local/AppDB.kt rename to app/src/main/java/com/informatika/bondoman/model/local/AppDatabase.kt index cef0784..bfd5fac 100644 --- a/app/src/main/java/com/informatika/bondoman/model/local/AppDB.kt +++ b/app/src/main/java/com/informatika/bondoman/model/local/AppDatabase.kt @@ -3,9 +3,9 @@ package com.informatika.bondoman.model.local import androidx.room.Database import androidx.room.RoomDatabase import com.informatika.bondoman.model.local.dao.TransactionDao -import com.informatika.bondoman.model.local.entity.Transaction +import com.informatika.bondoman.model.local.entity.transaction.Transaction -@Database(entities = [Transaction::class], version = DBConstants.mVersion) -abstract class AppDB : RoomDatabase() { +@Database(entities = [Transaction::class], version = DBConstants.mVersion, exportSchema = false) +abstract class AppDatabase : RoomDatabase() { abstract fun transactionDao(): TransactionDao } \ No newline at end of file diff --git a/app/src/main/java/com/informatika/bondoman/model/local/dao/TransactionDao.kt b/app/src/main/java/com/informatika/bondoman/model/local/dao/TransactionDao.kt index 6def362..88a853a 100644 --- a/app/src/main/java/com/informatika/bondoman/model/local/dao/TransactionDao.kt +++ b/app/src/main/java/com/informatika/bondoman/model/local/dao/TransactionDao.kt @@ -9,22 +9,22 @@ import com.informatika.bondoman.model.local.entity.transaction.Transaction @Dao interface TransactionDao { - @Query("SELECT * FROM " + DBConstants.mTableTransaction + " WHERE _id = :id") + @Query("SELECT * FROM `" + DBConstants.mTableTransaction + "` WHERE _id = :id") suspend fun get(id: Int): Transaction - @Query("SELECT * FROM " + DBConstants.mTableTransaction + " ORDER BY createdAt DESC") + @Query("SELECT * FROM `" + DBConstants.mTableTransaction + "` ORDER BY createdAt DESC") suspend fun getAll(): List<Transaction> - @Query("INSERT INTO " + DBConstants.mTableTransaction + " (title, category, amount, location) VALUES(:title, :category, :amount, :location)") + @Query("INSERT INTO `" + DBConstants.mTableTransaction + "` (title, category, amount, location) VALUES(:title, :category, :amount, :location)") suspend fun insert(title: String, category: Category, amount: Int, location: String) - @Query("INSERT INTO " + DBConstants.mTableTransaction + " (title, category, amount) VALUES(:title, :category, :amount)") + @Query("INSERT INTO `" + DBConstants.mTableTransaction + "` (title, category, amount) VALUES(:title, :category, :amount)") suspend fun insert(title: String, category: Category, amount: Int) - @Query("UPDATE " + DBConstants.mTableTransaction + " SET title = :title, amount = :amount, location = :location") + @Query("UPDATE `" + DBConstants.mTableTransaction + "` SET title = :title, amount = :amount, location = :location") suspend fun update(title: String, amount: Int, location: String) - @Query("UPDATE " + DBConstants.mTableTransaction + " SET title = :title, amount = :amount") + @Query("UPDATE `" + DBConstants.mTableTransaction + "` SET title = :title, amount = :amount") suspend fun update(title: String, amount: Int) @Delete diff --git a/app/src/main/java/com/informatika/bondoman/model/repository/login/LoginRepository.kt b/app/src/main/java/com/informatika/bondoman/model/repository/login/LoginRepository.kt new file mode 100644 index 0000000..a188e80 --- /dev/null +++ b/app/src/main/java/com/informatika/bondoman/model/repository/login/LoginRepository.kt @@ -0,0 +1,8 @@ +package com.informatika.bondoman.model.repository.login + +import com.informatika.bondoman.model.Resource +import com.informatika.bondoman.model.remote.AuthService + +interface LoginRepository { + suspend fun login(username: String, password: String): Resource<String> +} \ No newline at end of file diff --git a/app/src/main/java/com/informatika/bondoman/model/repository/LoginRepository.kt b/app/src/main/java/com/informatika/bondoman/model/repository/login/LoginRepositoryImpl.kt similarity index 79% rename from app/src/main/java/com/informatika/bondoman/model/repository/LoginRepository.kt rename to app/src/main/java/com/informatika/bondoman/model/repository/login/LoginRepositoryImpl.kt index eea2c0c..d732a57 100644 --- a/app/src/main/java/com/informatika/bondoman/model/repository/LoginRepository.kt +++ b/app/src/main/java/com/informatika/bondoman/model/repository/login/LoginRepositoryImpl.kt @@ -1,14 +1,13 @@ -package com.informatika.bondoman.model.repository +package com.informatika.bondoman.model.repository.login import com.informatika.bondoman.model.remote.request.LoginRequest -import com.informatika.bondoman.network.ApiClient import com.informatika.bondoman.model.Resource import com.informatika.bondoman.model.remote.AuthService import retrofit2.awaitResponse import java.io.IOException -class LoginRepository constructor(private val authService: AuthService) { - suspend fun login(username: String, password: String): Resource<String> { +class LoginRepositoryImpl constructor(private var authService: AuthService) : LoginRepository { + override suspend fun login(username: String, password: String): Resource<String> { try { val call = authService.login(LoginRequest(username, password)) val response = call.awaitResponse() diff --git a/app/src/main/java/com/informatika/bondoman/model/repository/token/TokenRepository.kt b/app/src/main/java/com/informatika/bondoman/model/repository/token/TokenRepository.kt new file mode 100644 index 0000000..201ac8b --- /dev/null +++ b/app/src/main/java/com/informatika/bondoman/model/repository/token/TokenRepository.kt @@ -0,0 +1,7 @@ +package com.informatika.bondoman.model.repository.token + +import com.informatika.bondoman.model.Resource + +interface TokenRepository { + suspend fun token(token: String): Resource<Boolean> +} \ No newline at end of file diff --git a/app/src/main/java/com/informatika/bondoman/model/repository/TokenRepository.kt b/app/src/main/java/com/informatika/bondoman/model/repository/token/TokenRepositoryImpl.kt similarity index 68% rename from app/src/main/java/com/informatika/bondoman/model/repository/TokenRepository.kt rename to app/src/main/java/com/informatika/bondoman/model/repository/token/TokenRepositoryImpl.kt index 1949258..babff4e 100644 --- a/app/src/main/java/com/informatika/bondoman/model/repository/TokenRepository.kt +++ b/app/src/main/java/com/informatika/bondoman/model/repository/token/TokenRepositoryImpl.kt @@ -1,15 +1,15 @@ -package com.informatika.bondoman.model.repository +package com.informatika.bondoman.model.repository.token -import android.util.Log import com.informatika.bondoman.network.ApiClient import com.informatika.bondoman.model.Resource +import com.informatika.bondoman.model.remote.AuthService import retrofit2.awaitResponse import timber.log.Timber -class TokenRepository { - suspend fun token(token: String): Resource<Boolean> { +class TokenRepositoryImpl(private var authService: AuthService) : TokenRepository { + override suspend fun token(token: String): Resource<Boolean> { try { - val call = ApiClient.authService.token("Bearer $token") + val call = authService.token("Bearer $token") val response = call.awaitResponse() Timber.tag("status").d(response.code().toString()) diff --git a/app/src/main/java/com/informatika/bondoman/model/repository/transaction/TransactionRepository.kt b/app/src/main/java/com/informatika/bondoman/model/repository/transaction/TransactionRepository.kt new file mode 100644 index 0000000..5c2608a --- /dev/null +++ b/app/src/main/java/com/informatika/bondoman/model/repository/transaction/TransactionRepository.kt @@ -0,0 +1,27 @@ +package com.informatika.bondoman.model.repository.transaction + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import com.informatika.bondoman.model.Resource +import com.informatika.bondoman.model.local.entity.transaction.Category +import com.informatika.bondoman.model.local.entity.transaction.Transaction + +interface TransactionRepository { + val _listTransactionLiveData: MutableLiveData<Resource<List<Transaction>>> + val listTransactionLiveData: LiveData<Resource<List<Transaction>>> + get() = listTransactionLiveData + + val _transactionLiveData: MutableLiveData<Resource<Transaction>> + val transactionLiveData: LiveData<Resource<Transaction>> + get() = transactionLiveData + + suspend fun getTransaction(id: Int) + suspend fun getAllTransaction() + suspend fun insertTransaction(title: String, category: Category, amount: Int, location: String) + suspend fun insertTransaction(title: String, category: Category, amount: Int) + suspend fun updateTransaction(title: String, amount: Int, location: String) + suspend fun updateTransaction(title: String, amount: Int) + suspend fun deleteTransaction(transaction: Transaction) + suspend fun refreshTransaction() + +} \ No newline at end of file diff --git a/app/src/main/java/com/informatika/bondoman/model/repository/TransactionRepository.kt b/app/src/main/java/com/informatika/bondoman/model/repository/transaction/TransactionRepositoryImpl.kt similarity index 55% rename from app/src/main/java/com/informatika/bondoman/model/repository/TransactionRepository.kt rename to app/src/main/java/com/informatika/bondoman/model/repository/transaction/TransactionRepositoryImpl.kt index dc4c074..66f4a7d 100644 --- a/app/src/main/java/com/informatika/bondoman/model/repository/TransactionRepository.kt +++ b/app/src/main/java/com/informatika/bondoman/model/repository/transaction/TransactionRepositoryImpl.kt @@ -1,4 +1,4 @@ -package com.informatika.bondoman.model.repository +package com.informatika.bondoman.model.repository.transaction import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData @@ -7,14 +7,20 @@ import com.informatika.bondoman.model.local.dao.TransactionDao import com.informatika.bondoman.model.local.entity.transaction.Category import com.informatika.bondoman.model.local.entity.transaction.Transaction -class TransactionRepository(private var transactionDao: TransactionDao) { - private val _listTransactionLiveData = MutableLiveData<Resource<List<Transaction>>>() - private val listTransactionLiveData : LiveData<Resource<List<Transaction>>> = _listTransactionLiveData +class TransactionRepositoryImpl(private var transactionDao: TransactionDao) : TransactionRepository { + override var _listTransactionLiveData = MutableLiveData<Resource<List<Transaction>>>() + private set + override var listTransactionLiveData : LiveData<Resource<List<Transaction>>> = _listTransactionLiveData + private set + get() = _listTransactionLiveData - private val _transactionLiveData = MutableLiveData<Resource<Transaction>>() - private val transactionLiveData : LiveData<Resource<Transaction>> = _transactionLiveData + override var _transactionLiveData = MutableLiveData<Resource<Transaction>>() + private set + override var transactionLiveData : LiveData<Resource<Transaction>> = _transactionLiveData + private set + get() = _transactionLiveData - suspend fun getTransaction(id: Int) { + override suspend fun getTransaction(id: Int) { _transactionLiveData.postValue(Resource.Loading()) try { val transaction = transactionDao.get(id) @@ -24,7 +30,7 @@ class TransactionRepository(private var transactionDao: TransactionDao) { } } - suspend fun getAllTransaction() { + override suspend fun getAllTransaction() { _listTransactionLiveData.postValue(Resource.Loading()) try { val transactionList = transactionDao.getAll() @@ -34,27 +40,27 @@ class TransactionRepository(private var transactionDao: TransactionDao) { } } - suspend fun insertTransaction(title: String, category: Category, amount: Int, location: String) { + override suspend fun insertTransaction(title: String, category: Category, amount: Int, location: String) { transactionDao.insert(title, category, amount, location) } - suspend fun insertTransaction(title: String, category: Category, amount: Int) { + override suspend fun insertTransaction(title: String, category: Category, amount: Int) { transactionDao.insert(title, category, amount) } - suspend fun updateTransaction(title: String, amount: Int, location: String) { + override suspend fun updateTransaction(title: String, amount: Int, location: String) { transactionDao.update(title, amount, location) } - suspend fun updateTransaction(title: String, amount: Int) { + override suspend fun updateTransaction(title: String, amount: Int) { transactionDao.update(title, amount) } - suspend fun deleteTransaction(transaction: Transaction) { + override suspend fun deleteTransaction(transaction: Transaction) { transactionDao.delete(transaction) } - suspend fun refreshTransaction() { + override suspend fun refreshTransaction() { _listTransactionLiveData.postValue(Resource.Loading()) try { val transactionList = transactionDao.getAll() @@ -63,8 +69,4 @@ class TransactionRepository(private var transactionDao: TransactionDao) { _listTransactionLiveData.postValue(Resource.Error(e)) } } - - fun getListTransactionLiveData() = listTransactionLiveData - - fun getTransactionLiveData() = transactionLiveData } \ No newline at end of file diff --git a/app/src/main/java/com/informatika/bondoman/prefdatastore/JWTManager.kt b/app/src/main/java/com/informatika/bondoman/prefdatastore/JWTManager.kt index 1e232f6..e7f9a28 100644 --- a/app/src/main/java/com/informatika/bondoman/prefdatastore/JWTManager.kt +++ b/app/src/main/java/com/informatika/bondoman/prefdatastore/JWTManager.kt @@ -1,65 +1,13 @@ package com.informatika.bondoman.prefdatastore -import android.content.Context -import androidx.datastore.core.DataStore -import androidx.datastore.preferences.core.Preferences -import androidx.datastore.preferences.core.edit -import androidx.datastore.preferences.core.stringPreferencesKey -import androidx.datastore.preferences.preferencesDataStore -import com.informatika.bondoman.model.Resource -import com.informatika.bondoman.model.repository.TokenRepository import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.firstOrNull -import kotlinx.coroutines.flow.map -const val KEY_TOKEN = "jwt_token" -val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = KEY_TOKEN) +interface JWTManager { + fun isAuthenticated(): Flow<Boolean> -class JWTManager(private val context: Context) { - private val tokenRepository = TokenRepository() + suspend fun getToken(): String - // returns a flow of is authenticated state - fun isAuthenticated(): Flow<Boolean> { - // flow of token existence from dataStore - return context.dataStore.data.map { - it.contains(JWT_TOKEN) - } - } + suspend fun saveToken(token: String) - suspend fun getToken(): String { - return context.dataStore.data - .map { it[JWT_TOKEN] } // get a flow of token from dataStore - .firstOrNull() // transform flow to suspend - ?: throw IllegalArgumentException("no token stored") - } - - // store new token after sign in or token refresh - suspend fun saveToken(token: String) { - context.dataStore.edit { - it[JWT_TOKEN] = token - } - } - - suspend fun isExpired(): Boolean { - return when (val result = tokenRepository.token(getToken())) { - is Resource.Success -> { - !result.data - } - - else -> { - true - } - } - } - - // to call when user logs out or when refreshing the token has failed - suspend fun onLogout() { - context.dataStore.edit { - it.remove(JWT_TOKEN) - } - } - - companion object { - val JWT_TOKEN = stringPreferencesKey(KEY_TOKEN) - } + suspend fun onLogout() } \ No newline at end of file diff --git a/app/src/main/java/com/informatika/bondoman/prefdatastore/JWTManagerImpl.kt b/app/src/main/java/com/informatika/bondoman/prefdatastore/JWTManagerImpl.kt new file mode 100644 index 0000000..c7a5016 --- /dev/null +++ b/app/src/main/java/com/informatika/bondoman/prefdatastore/JWTManagerImpl.kt @@ -0,0 +1,50 @@ +package com.informatika.bondoman.prefdatastore + +import android.content.Context +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.core.stringPreferencesKey +import androidx.datastore.preferences.preferencesDataStore +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.flow.map + +const val KEY_TOKEN = "jwt_token" +val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = KEY_TOKEN) + +class JWTManagerImpl(private val context: Context) : JWTManager { + + // returns a flow of is authenticated state + override fun isAuthenticated(): Flow<Boolean> { + // flow of token existence from dataStore + return context.dataStore.data.map { + it.contains(JWT_TOKEN) + } + } + + override suspend fun getToken(): String { + return context.dataStore.data + .map { it[JWT_TOKEN] } // get a flow of token from dataStore + .firstOrNull() // transform flow to suspend + ?: throw IllegalArgumentException("no token stored") + } + + // store new token after sign in or token refresh + override suspend fun saveToken(token: String) { + context.dataStore.edit { + it[JWT_TOKEN] = token + } + } + + // to call when user logs out or when refreshing the token has failed + override suspend fun onLogout() { + context.dataStore.edit { + it.remove(JWT_TOKEN) + } + } + + companion object { + val JWT_TOKEN = stringPreferencesKey(KEY_TOKEN) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/informatika/bondoman/view/activity/LoginActivity.kt b/app/src/main/java/com/informatika/bondoman/view/activity/LoginActivity.kt index 38fbe04..dfb2b29 100644 --- a/app/src/main/java/com/informatika/bondoman/view/activity/LoginActivity.kt +++ b/app/src/main/java/com/informatika/bondoman/view/activity/LoginActivity.kt @@ -12,23 +12,33 @@ import android.view.View import android.view.inputmethod.EditorInfo import android.widget.EditText import android.widget.Toast -import androidx.activity.viewModels import com.informatika.bondoman.MainActivity import com.informatika.bondoman.databinding.ActivityLoginBinding import com.informatika.bondoman.R import com.informatika.bondoman.prefdatastore.JWTManager +import com.informatika.bondoman.prefdatastore.JWTManagerImpl +import com.informatika.bondoman.viewmodel.JWTViewModel import com.informatika.bondoman.viewmodel.login.LoginViewModel import kotlinx.coroutines.runBlocking +import org.koin.android.ext.koin.androidContext +import org.koin.androidx.viewmodel.ext.android.viewModel +import org.koin.core.parameter.parametersOf +import org.koin.core.context.startKoin +import timber.log.Timber class LoginActivity : AppCompatActivity() { - private val loginViewModel: LoginViewModel by viewModels<LoginViewModel>() + private val loginViewModel: LoginViewModel by viewModel() private lateinit var binding: ActivityLoginBinding - private lateinit var jwtManager: JWTManager + private val jwtViewModel: JWTViewModel by viewModel() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) +// initTimber() +// initKoin() + // koin set android context + binding = ActivityLoginBinding.inflate(layoutInflater) setContentView(binding.root) @@ -37,7 +47,6 @@ class LoginActivity : AppCompatActivity() { val login = binding.login val loading = binding.loading - jwtManager = JWTManager(applicationContext) loginViewModel.loginFormState.observe(this@LoginActivity, Observer { val loginState = it ?: return@Observer @@ -59,9 +68,8 @@ class LoginActivity : AppCompatActivity() { } else { if (loginResult.jwtToken != null) { runBlocking { - jwtManager.saveToken(loginResult.jwtToken) + jwtViewModel.jwtManager.saveToken(loginResult.jwtToken) } - updateUiWithUser() val intent = Intent(this@LoginActivity, MainActivity::class.java) startActivity(intent) @@ -129,4 +137,5 @@ class LoginActivity : AppCompatActivity() { override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {} }) } + } \ No newline at end of file diff --git a/app/src/main/java/com/informatika/bondoman/view/activity/MainEmptyActivity.kt b/app/src/main/java/com/informatika/bondoman/view/activity/MainEmptyActivity.kt index 9e732be..3c7f5b6 100644 --- a/app/src/main/java/com/informatika/bondoman/view/activity/MainEmptyActivity.kt +++ b/app/src/main/java/com/informatika/bondoman/view/activity/MainEmptyActivity.kt @@ -10,17 +10,21 @@ import androidx.lifecycle.lifecycleScope import com.informatika.bondoman.BuildConfig import com.informatika.bondoman.MainActivity import com.informatika.bondoman.databinding.ActivityMainEmptyBinding +import com.informatika.bondoman.di.applicationModule import com.informatika.bondoman.di.databaseModule import com.informatika.bondoman.di.networkModule +import com.informatika.bondoman.di.repositoryModule import com.informatika.bondoman.di.viewModelModule import com.informatika.bondoman.prefdatastore.JWTManager +import com.informatika.bondoman.viewmodel.JWTViewModel import kotlinx.coroutines.launch import org.koin.android.ext.koin.androidContext +import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.core.context.startKoin import timber.log.Timber class MainEmptyActivity : AppCompatActivity() { - private lateinit var jwtManager: JWTManager + private val jwtViewModel: JWTViewModel by viewModel() private lateinit var binding: ActivityMainEmptyBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -38,30 +42,36 @@ class MainEmptyActivity : AppCompatActivity() { installSplashScreen() enableEdgeToEdge() + } - jwtManager = JWTManager(applicationContext) - lifecycleScope.launch { - jwtManager.isAuthenticated().collect { - if (it) { - val intent = Intent(this@MainEmptyActivity, MainActivity::class.java) - startActivity(intent) - finish() + override fun onStart() { + super.onStart() + if (BuildConfig.DEBUG) { + Timber.d("onStart") + } - } else { - val intent = Intent(this@MainEmptyActivity, LoginActivity::class.java) - startActivity(intent) - finish() - } + lifecycleScope.launch { + if (jwtViewModel.isExpired()) { + val intent = Intent(this@MainEmptyActivity, LoginActivity::class.java) + startActivity(intent) + finish() + } else { + val intent = Intent(this@MainEmptyActivity, MainActivity::class.java) + startActivity(intent) + finish() } } } private fun initKoin() { startKoin { + androidContext(this@MainEmptyActivity) modules( listOf( + applicationModule, databaseModule, networkModule, + repositoryModule, viewModelModule ) ) diff --git a/app/src/main/java/com/informatika/bondoman/view/fragment/TransactionFragment.kt b/app/src/main/java/com/informatika/bondoman/view/fragment/TransactionFragment.kt index 9cfd710..deb0504 100644 --- a/app/src/main/java/com/informatika/bondoman/view/fragment/TransactionFragment.kt +++ b/app/src/main/java/com/informatika/bondoman/view/fragment/TransactionFragment.kt @@ -5,18 +5,26 @@ import androidx.fragment.app.Fragment import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.fragment.app.viewModels +import org.koin.androidx.viewmodel.ext.android.viewModel import com.informatika.bondoman.R +import com.informatika.bondoman.viewmodel.transaction.CreateTransactionViewModel +import com.informatika.bondoman.viewmodel.transaction.DetailTransactionViewModel import com.informatika.bondoman.viewmodel.transaction.ListTransactionViewModel +import com.informatika.bondoman.viewmodel.transaction.UpdateTransactionViewModel +import org.koin.core.parameter.parametersOf class TransactionFragment : Fragment() { + val _id: Int = 0 + // TODO: delete unused viewModels just dummy + private val listTransactionViewModel: ListTransactionViewModel by viewModel() + private val detailTransactionViewModel: DetailTransactionViewModel by viewModel { parametersOf(_id) } + private val updateTransactionViewModel: UpdateTransactionViewModel by viewModel { parametersOf(_id) } + private val createTransactionViewModel: CreateTransactionViewModel by viewModel() companion object { fun newInstance() = TransactionFragment() } - private val viewModel: ListTransactionViewModel by viewModels() - override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? diff --git a/app/src/main/java/com/informatika/bondoman/viewmodel/JWTViewModel.kt b/app/src/main/java/com/informatika/bondoman/viewmodel/JWTViewModel.kt new file mode 100644 index 0000000..71fcb34 --- /dev/null +++ b/app/src/main/java/com/informatika/bondoman/viewmodel/JWTViewModel.kt @@ -0,0 +1,28 @@ +package com.informatika.bondoman.viewmodel + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.informatika.bondoman.model.Resource +import com.informatika.bondoman.model.repository.token.TokenRepository +import com.informatika.bondoman.prefdatastore.JWTManager +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import timber.log.Timber + +class JWTViewModel(var jwtManager: JWTManager, private var tokenRepository: TokenRepository): ViewModel() { + suspend fun isExpired(): Boolean { + var isExpired = true + try { + val result = tokenRepository.token(jwtManager.getToken()) + if (result is Resource.Success) { + isExpired = false + } else { + jwtManager.onLogout() + } + } catch (e: Exception) { + Timber.tag("JWT").d("No token found") + } + return isExpired + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/informatika/bondoman/viewmodel/login/LoginViewModel.kt b/app/src/main/java/com/informatika/bondoman/viewmodel/login/LoginViewModel.kt index 355b484..0b3bf85 100644 --- a/app/src/main/java/com/informatika/bondoman/viewmodel/login/LoginViewModel.kt +++ b/app/src/main/java/com/informatika/bondoman/viewmodel/login/LoginViewModel.kt @@ -4,12 +4,12 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import android.util.Patterns -import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope -import com.informatika.bondoman.model.repository.LoginRepository +import com.informatika.bondoman.model.repository.login.LoginRepositoryImpl import com.informatika.bondoman.model.Resource import com.informatika.bondoman.R +import com.informatika.bondoman.model.repository.login.LoginRepository import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch diff --git a/app/src/main/java/com/informatika/bondoman/viewmodel/transaction/CreateTransactionViewModel.kt b/app/src/main/java/com/informatika/bondoman/viewmodel/transaction/CreateTransactionViewModel.kt index e8296e5..6453553 100644 --- a/app/src/main/java/com/informatika/bondoman/viewmodel/transaction/CreateTransactionViewModel.kt +++ b/app/src/main/java/com/informatika/bondoman/viewmodel/transaction/CreateTransactionViewModel.kt @@ -6,7 +6,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.informatika.bondoman.R import com.informatika.bondoman.model.local.entity.transaction.Category -import com.informatika.bondoman.model.repository.TransactionRepository +import com.informatika.bondoman.model.repository.transaction.TransactionRepository import com.informatika.bondoman.viewmodel.transaction.helper.TransactionFormState import kotlinx.coroutines.launch diff --git a/app/src/main/java/com/informatika/bondoman/viewmodel/transaction/DetailTransactionViewModel.kt b/app/src/main/java/com/informatika/bondoman/viewmodel/transaction/DetailTransactionViewModel.kt index de71945..eda6c43 100644 --- a/app/src/main/java/com/informatika/bondoman/viewmodel/transaction/DetailTransactionViewModel.kt +++ b/app/src/main/java/com/informatika/bondoman/viewmodel/transaction/DetailTransactionViewModel.kt @@ -5,24 +5,24 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.informatika.bondoman.model.Resource import com.informatika.bondoman.model.local.entity.transaction.Transaction -import com.informatika.bondoman.model.repository.TransactionRepository +import com.informatika.bondoman.model.repository.transaction.TransactionRepository import kotlinx.coroutines.launch -class DetailTransactionViewModel(private var transactionRepository: TransactionRepository, transaction: Transaction) : ViewModel() { +class DetailTransactionViewModel(private var transactionRepository: TransactionRepository, _id: Int) : ViewModel() { val transactionLiveData = MutableLiveData<Resource<Transaction>>() private val observer = androidx.lifecycle.Observer<Resource<Transaction>> { transactionLiveData.postValue(it) } init { - transactionRepository.getTransactionLiveData().observeForever(observer) + transactionRepository.transactionLiveData.observeForever(observer) viewModelScope.launch { - transactionRepository.getTransaction(transaction._id) + transactionRepository.getTransaction(_id) } } override fun onCleared() { super.onCleared() - transactionRepository.getTransactionLiveData().removeObserver(observer) + transactionRepository.transactionLiveData.removeObserver(observer) } } \ No newline at end of file diff --git a/app/src/main/java/com/informatika/bondoman/viewmodel/transaction/ListTransactionViewModel.kt b/app/src/main/java/com/informatika/bondoman/viewmodel/transaction/ListTransactionViewModel.kt index 279d0e1..99501a6 100644 --- a/app/src/main/java/com/informatika/bondoman/viewmodel/transaction/ListTransactionViewModel.kt +++ b/app/src/main/java/com/informatika/bondoman/viewmodel/transaction/ListTransactionViewModel.kt @@ -5,7 +5,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.informatika.bondoman.model.Resource import com.informatika.bondoman.model.local.entity.transaction.Transaction -import com.informatika.bondoman.model.repository.TransactionRepository +import com.informatika.bondoman.model.repository.transaction.TransactionRepository import kotlinx.coroutines.launch class ListTransactionViewModel(private var transactionRepository: TransactionRepository) : ViewModel() { @@ -15,7 +15,7 @@ class ListTransactionViewModel(private var transactionRepository: TransactionRep } init { - transactionRepository.getListTransactionLiveData().observeForever(observer) + transactionRepository.listTransactionLiveData.observeForever(observer) } fun getAllTransaction() { @@ -26,7 +26,7 @@ class ListTransactionViewModel(private var transactionRepository: TransactionRep override fun onCleared() { super.onCleared() - transactionRepository.getListTransactionLiveData().removeObserver(observer) + transactionRepository.listTransactionLiveData.removeObserver(observer) } diff --git a/app/src/main/java/com/informatika/bondoman/viewmodel/transaction/UpdateTransactionViewModel.kt b/app/src/main/java/com/informatika/bondoman/viewmodel/transaction/UpdateTransactionViewModel.kt index 16da0cb..6576ced 100644 --- a/app/src/main/java/com/informatika/bondoman/viewmodel/transaction/UpdateTransactionViewModel.kt +++ b/app/src/main/java/com/informatika/bondoman/viewmodel/transaction/UpdateTransactionViewModel.kt @@ -5,27 +5,28 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.informatika.bondoman.model.Resource import com.informatika.bondoman.model.local.entity.transaction.Transaction -import com.informatika.bondoman.model.repository.TransactionRepository +import com.informatika.bondoman.model.repository.transaction.TransactionRepository import com.informatika.bondoman.viewmodel.transaction.helper.TransactionFormState import kotlinx.coroutines.launch -class UpdateTransactionViewModel(private var transactionRepository: TransactionRepository, transaction: Transaction) : ViewModel() { +class UpdateTransactionViewModel(private var transactionRepository: TransactionRepository, _id: Int) : ViewModel() { val transactionLiveData = MutableLiveData<Resource<Transaction>>() private val observer = androidx.lifecycle.Observer<Resource<Transaction>> { transactionLiveData.postValue(it) } + // TODO: Implement updateTransaction function private val _updateTransactionForm = MutableLiveData<TransactionFormState>() init { - transactionRepository.getTransactionLiveData().observeForever(observer) + transactionRepository.transactionLiveData.observeForever(observer) viewModelScope.launch { - transactionRepository.getTransaction(transaction._id) + transactionRepository.getTransaction(_id) } } override fun onCleared() { super.onCleared() - transactionRepository.getTransactionLiveData().removeObserver(observer) + transactionRepository.transactionLiveData.removeObserver(observer) } } \ No newline at end of file diff --git a/app/src/test/java/com/informatika/bondoman/CheckAllModules.kt b/app/src/test/java/com/informatika/bondoman/CheckAllModules.kt new file mode 100644 index 0000000..9f1ea55 --- /dev/null +++ b/app/src/test/java/com/informatika/bondoman/CheckAllModules.kt @@ -0,0 +1,28 @@ +package com.informatika.bondoman + +import android.app.Application +import com.informatika.bondoman.di.applicationModule +import com.informatika.bondoman.di.databaseModule +import com.informatika.bondoman.di.networkModule +import com.informatika.bondoman.di.repositoryModule +import com.informatika.bondoman.di.viewModelModule +import io.mockk.mockk +import org.koin.android.ext.koin.androidContext +import org.koin.core.context.startKoin +import org.koin.dsl.koinApplication +import org.koin.test.KoinTest +import org.koin.test.check.checkModules +import kotlin.test.Test + +class CheckAllModules: KoinTest { + private val mockApplication: Application = mockk() + @Test + fun checkAllModules() { + // Check all modules + startKoin { + androidContext(mockApplication) + modules(listOf(applicationModule, databaseModule, networkModule, repositoryModule, viewModelModule)) + }.checkModules() + + } +} \ No newline at end of file -- GitLab