Repository Pattern in Android. Is there any way better than this? - android

I have an app based on MVVM architecture.
I have two modules that repository and datasource.
And using coroutines. I came across some projects on Github they applied different ways.
My implementation like this;
You can think naming as login, profile etc. instead of X letter
datasource interface
interface IXDatasource{
suspend fun fetchData(): LiveData<XResponse>
}
datasource implementation
class XDatasourceImp #Inject constructor(private val apiService: ApiService) : IXDatasource{
suspend fun fetchData(): LiveData<XResponse> {
// getting data
return xResponse
}
}
repository interface
interface XRepository {
suspend fun getXData(): LiveData<XResponse>
}
repository implementation
class XRepositoryImp #Inject constructor(private val iDatasource: IXDatasource): XRepository {
override suspend fun getXData(): LiveData<XResponse> {
return withContext(Dispatchers.IO) {
iDatasource.fetchData()
}
}
}
I called this in my ViewModel
class XViewModel #Inject constructor(xRepository: XRepository) : BaseViewModel() {
val xModel by lazyDeferred {
xRepository.getXData()
}
}
And I use in my activity/fragment
private fun init() = launch {
val xModel = viewModel.xModel.await()
xModel.observe(this#MyActivity, Observer {
if (it == null) {
return#Observer
}
// filling views or doing sth
})
}
It works but I wonder all of them is required or not? I can apply the room database to my datasource. Is there any way better than this? I know it can change by the case of the app. I try to find the best way. I will be happy if you suggest anything or share anything about repository pattern implementation.

Related

Android should you really use by Lazy in ViewModel as per Docs?

This Android doc shows following code:
class MyViewModel : ViewModel() {
private val users: MutableLiveData<List<User>> by lazy {
MutableLiveData<List<User>>().also {
loadUsers()
}
}
fun getUsers(): LiveData<List<User>> {
return users
}
private fun loadUsers() {
// Do an asynchronous operation to fetch users.
}
}
and I'm wondering if it is really correct to use by lazy or really needed since I can not encounter any projects using this. If you do use it and you for example do following:
private fun loadUsers() {
// Do an asynchronous operation to fetch users.
users.value = ...
}
the code access "users" again and since it is not initialized yet loadUsers() is called again! Considering you observe users in an activity or fragment for example.

Kotlin Room withTransaction in Repository or #Transaction in Dao

I have a question regarding Room and it’s withTransaction { } code block in combination with Koin.
I have a repository where I need to access a couple of DAOs at the same time. I wanted to work with a withTransaction { } so I wouldn’t clutter 1 DAO with references to other DAOs.
I’m not sure which object to inject in the constructor of my repository. The withTransaction{ } can only be accessed by getting the RoomDatabase. But having the RoomDatabase in my Repository means that I have access to all the DAOs connected to that RoomDatabase. I’m not sure what the best practice around this use-case would be.
Should I use the withTransaction { } and risk that all DAOs are accessible be that Repository or should I have the DAOs in my Repository's constructor and hand them to the `ReviewDao' to handle every insert?
An example would be something like this with withTransaction { }
class ReviewRepository(
private val roomDatabase: RoomDatabase
) {
private val reviewDao = roomDatabase.reviewDao()
private val userDao = roomDatabase.userDao()
suspend fun saveReview(reviewResponse: ReviewResponse) {
roomDatabase.withTransaction {
reviewDao.insert(reviewResponse.getAsEntity())
userDao.insert(reviewResponse.user.getAsEntity())
}
}
}
And an example without withTransaction { } would be this
class ReviewRepository(
private val reviewDao : ReviewDao,
private val userDao : UserDao
) {
suspend fun saveReview(reviewResponse: ReviewResponse) {
reviewDao.insertWithUser(reviewResponse.getAsEntity(), reviewResponse.user.getAsEntity(), userDao)
}
}
#Dao
interface ReviewDao {
#Transaction
suspend fun insertWithUser(review: Review, user: User, userDao: UserDao) {
insert(review)
userDao.insert(user)
}
}
The solution that worked for me was to use a separate class that takes in the database in the constructor and provides an extension to the withTransaction.
class TransactionProvider(
private val db: AppDatabase
) {
suspend fun <R> runAsTransaction(block: suspend () -> R): R {
return db.withTransaction(block)
}
}
I can then inject this class into the Repository I need without providing the complete database object to that Repository.
I would go with a variant of your Repository solution. In a project, we had UseCase and Handler structures. To simply put;
class SaveReviewHandler(private val db: RoomDatabase) {
private val reviewDao = db.reviewDao()
private val userDao = db.userDao()
suspend fun execute(useCase: SaveReview) {
db.withTransaction {
reviewDao.insert(useCase.review)
userDao.insert(useCase.user)
}
}
}
data class SaveReview(review: ReviewEntitry, user: UserEntity)
and we can call it as follows:
saveReviewHandler.execute(SaveReview(reviewResponse.getAsEntity(),reviewResponse.user.getAsEntity()))
One good side of this architecture is; your repository may have other complex transactions, but it does not have to have direct access to DAOs.

Kotlin Flow: callbackFlow with lazy initializer of callback object

I want to use reactive paradigm using Kotlin Flow in my Android project. I have an external callback-based API so my choice is using callbackFlow in my Repository class.
I've already read insightfully some proper docs with no help:
callbackFlow documentation
Callbacks and Kotlin Flows by Roman Elizarov
What I want to achieve:
Currently my Repository class looks like this (simplified code):
lateinit var callback: ApiCallback
fun someFlow() = callbackFlow<SomeModel> {
callback = object : ApiCallback {
override fun someApiMethod() {
offer(SomeModel())
}
}
awaitClose { Log.d("Suspending flow until methods aren't invoked") }
}
suspend fun someUnfortunateCallbackDependentCall() {
externalApiClient.externalMethod(callback)
}
Problem occurs when someUnfortunateCallbackDependentCall is invoked faster than collecting someFlow().
For now to avoid UninitializedPropertyAccessException I added some delays in my coroutines before invoking someUnfortunateCallbackDependentCall but it is kind of hack/code smell for me.
My first idea was to use by lazy instead of lateinit var as this is what I want - lazy initialization of callback object. However, I couldn't manage to code it altogether. I want to emit/offer/send some data from someApiMethod to make a data flow but going outside of callbackFlow would require ProducerScope that is in it. And on the other hand, someUnfortunateCallbackDependentCall is not Kotlin Flow-based at all (could be suspended using Coroutines API at best).
Is it possible to do? Maybe using some others Kotlin delegates? Any help would be appreciated.
To answer your question technically, you can of course intialise a callback lazyily or with lateinit, but you can't do this AND share the coroutine scope (one for the Flow and one for the suspend function) at the same time - you need to build some kind of synchronisation yourself.
Below I've made some assumptions about what you are trying to achieve, perhaps they are not perfect for you, but hopefully give some incite into how to improve.
Since it is a Repository that you are creating, I will first assume that you are looking to store SomeModel and allow the rest of your app to observe changes to it. If so, the easiest way to do this is with a MutableStateFlow property instead of a callbackFlow:
interface Repository {
val state: Flow<SomeModel>
suspend fun reload()
}
class RepositoryImpl(private val service: ApiService) : Repository {
override val state = MutableStateFlow(SomeModel())
override suspend fun reload() {
return suspendCoroutine { continuation ->
service.callBackend(object : ApiCallback {
override fun someApiMethod(data: SomeModel) {
state.value = data
if (continuation.context.isActive)
continuation.resume(Unit)
}
})
}
}
}
interface ApiCallback {
fun someApiMethod(data: SomeModel)
}
data class SomeModel(val data: String = "")
interface ApiService {
fun callBackend(callback: ApiCallback)
}
The downside to this solution is that you have to call reload() in order to actually make a call to your backend, collecting the Flow alone is not enough.
myrepository.state.collect {}
myrepository.reload()
Another solution, again depending on what exactly you are trying to achieve, is to provide two ways to call your backend:
interface Repository {
fun someFlow(): Flow<SomeModel>
suspend fun reload(): SomeModel
}
class RepositoryImpl(private val service: ApiService) : Repository {
override fun someFlow() = callbackFlow<SomeModel> {
service.callBackend(object : ApiCallback {
override fun someApiMethod(data: SomeModel) {
offer(data)
}
})
awaitClose {
Log.d("TAG", "Callback Flow is closed")
}
}
override suspend fun reload(): SomeModel {
return suspendCoroutine<SomeModel> { continuation ->
service.callBackend(object : ApiCallback {
override fun someApiMethod(data: SomeModel) {
if (continuation.context.isActive)
continuation.resume(data)
}
})
}
}
}
interface ApiCallback {
fun someApiMethod(data: SomeModel)
}
data class SomeModel(val data: String = "")
interface ApiService {
fun callBackend(callback: ApiCallback)
}
Now you can either call reload() or someFlow() to retrieve SomeModel() and the Repository holds no "state".
Note that the reload() function is simply a 'coroutine' version of the callbackFlow idea.

How can inject interactor from presenter with Koin

I'm new at Koin. I have set all the stuff and is working. But I'm getting some problems when I'm trying to inject interactor and presenter at the same time. That not sure it is possible.
This is my Module
val applicationModule = module(override = true) {
factory{VoucherImpl(get())}
factory<VoucherContract.Presenter> { (view: VoucherContract.View) -> VoucherPresenter(view, get()) }
}
This is my Activity where inject the presenter
private val presenter: VoucherContract.Presenter by inject { parametersOf(this)}
This is my Presenter
class VoucherPresenter (private var view: VoucherContract.View?, private var mCodeRechargeInteract : VoucherImpl) : VoucherContract.Presenter, VoucherContract.Callback, KoinComponent {
override fun create() {
view?.initView()
view?.showProgress()
mCodeRechargeInteract.run()
}
.
.
.
Interactor class
class VoucherImpl(private var mCallback: VoucherContract.Callback?) : AbstractInteractor() {
.
.
.
contract
interface VoucherContract {
interface Presenter {
fun create()
fun destroy()
fun checkIfShoppingCartHaveItems()
fun addVoucherToShoppingCart(voucherProduct: Product)
fun onItemClick(product: Product)
}
interface Callback {
fun onResponseVouchers(vouchers: List<Product>?)
fun onError()
}
}
With this code I get
No definition found for 'xxx.voucher.VoucherContract$Callback' has been found. Check your module definitions.
Then, I try to put it in the module and I can't do it because I get: a Type mismatch. Required VoucherContract.Callback Found VoucherImpl
factory<VoucherContract.Callback> { (callBack: VoucherContract.Callback) -> VoucherImpl(callBack) }
You have a circular dependency that's why this doesn't work.
VoucherImpl(VoucherContract.Callback) and VoucherPresenter(View, VoucherImpl):VoucherContract.Callback
There are multiple ways out of this predicament.
I would recommend the following changes:
The VoucherImpl should not have the constructor parameter VoucherContract.Callback. This callback should be the parameter of a method something like this:
class VoucherImpl : AbstractInteractor(){
fun listen(VoucherContract.Callback){...}
}
This way the dependency becomes one way and you can inject them.

MVVM architecture with Interactors/UseCases

Context
So, I've been working with the MVVM architecture just for a couple of projects. I'm still trying to figure out and improve how the architecture works. I always worked with the MVP architecture, using the usual toolset, Dagger for DI, usually multi-module projects, the Presenter layer being injected with a bunch of Interactors/UseCases, and each Interactor being injected with different Repositories to perform the backend API calls.
Now that I've moved into MVVM I changed the Presenter layer by the ViewModel, the communication from the ViewModel to the UI layer is being done through LiveData instead of using a View callback interface, and so on.
Looks like this:
class ProductDetailViewModel #inject constructor(
private val getProductsUseCase: GetProductsUseCase,
private val getUserInfoUseCase: GetUserInfoUseCase,
) : ViewModel(), GetProductsUseCase.Callback, GetUserInfoUseCase.Callback {
// Sealed class used to represent the state of the ViewModel
sealed class ProductDetailViewState {
data class UserInfoFetched(
val userInfo: UserInfo
) : ProductDetailViewState(),
data class ProductListFetched(
val products: List<Product>
) : ProductDetailViewState(),
object ErrorFetchingInfo : ProductDetailViewState()
object LoadingInfo : ProductDetailViewState()
}
...
// Live data to communicate back with the UI layer
val state = MutableLiveData<ProductDetailViewState>()
...
// region Implementation of the UseCases callbacks
override fun onSuccessfullyFetchedProducts(products: List<Product>) {
state.value = ProductDetailViewState.ProductListFetched(products)
}
override fun onErrorFetchingProducts(e: Exception) {
state.value = ProductDetailViewState.ErrorFetchingInfo
}
override fun onSuccessfullyFetchedUserInfo(userInfo: UserInfo) {
state.value = ProductDetailViewState.UserInfoFetched(userInfo)
}
override fun onErrorFetchingUserInfo(e: Exception) {
state.value = ProductDetailViewState.ErrorFetchingInfo
}
// Functions to call the UseCases from the UI layer
fun fetchUserProductInfo() {
state.value = ProductDetailViewState.LoadingInfo
getProductsUseCase.execute(this)
getUserInfoUseCase.execute(this)
}
}
There's no rocket science here, sometimes I change the implementation to use more than one LiveData property to keep track of the changes. By the way, this is just an example that I wrote on the fly, so don't expect it to compile. But It's just this, the ViewModel is injected with a bunch of UseCases, it implements the UseCases callback interfaces and when I get the results from the UseCases I communicate that to the UI layer through LiveData.
My UseCases usually look like this:
// UseCase interface
interface GetProductsUseCase {
interface Callback {
fun onSuccessfullyFetchedProducts(products: List<Product>)
fun onErrorFetchingProducts(e: Exception)
}
fun execute(callback: Callback)
}
// Actual implementation
class GetProductsUseCaseImpl(
private val productRepository: ApiProductRepostory
) : GetProductsUseCase {
override fun execute(callback: Callback) {
productRepository.fetchProducts() // Fetches the products from the backend through Retrofit
.subscribe(
{
// onNext()
callback.onSuccessfullyFetchedProducts(it)
},
{
// onError()
callback.onErrorFetchingProducts(it)
}
)
}
}
My Repository classes are usually wrappers for the Retrofit instance and they take care of setting the proper Scheduler so everything runs on the proper thread and mapping the backend responses into model classes. By backend responses I mean classes mapped with Gson (for example
a list of ApiProductResponse) and they get mapped into model classes (for example a List of Product which I use across the App)
Question
My question here is that since I started working with the MVVM architecture all the articles and all the examples, people is either injecting the Repositories right into the ViewModel (duplicating code to handle errors and mapping the responses) or either using the Single Source of Truth pattern (getting the information from Room using Room's Flowables). But I haven't seen anyone use UseCases with a ViewModel layer. I mean it's pretty handy, I get to keep things separated, I do the mapping of the backend responses within the UseCases, I handle any error there. But still, feels odds that I don't see anyone doing this, is there some way to improve the UseCases to make them more friendly to the ViewModels in terms of API? Perform the communication between the UseCases and the ViewModels with something else than a callback interface?
Please let me know if you need any more info about this. Sorry for the examples, I know that these are not the best, I just came out with something simple for sake of explaining it better.
Thanks,
Edit #1
This is how my Repository classes look like:
// ApiProductRepository interface
interface ApiProductRepository {
fun fetchProducts(): Single<NetworkResponse<List<ApiProductResponse>>>
}
// Actual implementation
class ApiProductRepositoryImpl(
private val retrofitApi: ApiProducts, // This is a Retrofit API interface
private val uiScheduler: Scheduler, // AndroidSchedulers.mainThread()
private val backgroundScheduler: Scheduler, // Schedulers.io()
) : GetProductsUseCase {
override fun fetchProducts(): Single<NetworkResponse<List<ApiProductResponse>>> {
return retrofitApi.fetchProducts() // Does the API call using the Retrofit interface. I've the RxAdapter set.
.wrapOnNetworkResponse() // Extended function that converts the Retrofit's Response object into a NetworkResponse class
.observeOn(uiScheduler)
.subscribeOn(backgroundScheduler)
}
}
// The network response class is a class that just carries the Retrofit's Response class status code
Update your use case so that it returns Single<List<Product>>:
class GetProducts #Inject constructor(private val repository: ApiProductRepository) {
operator fun invoke(): Single<List<Product>> {
return repository.fetchProducts()
}
}
Then, update your ViewModel so that it subscribes to the products stream:
class ProductDetailViewModel #Inject constructor(
private val getProducts: GetProducts
): ViewModel() {
val state: LiveData<ProductDetailViewState> get() = _state
private val _state = MutableLiveData<ProductDetailViewState>()
private val compositeDisposable = CompositeDisposable()
init {
subscribeToProducts()
}
override fun onCleared() {
super.onCleared()
compositeDisposable.clear()
}
private fun subscribeToProducts() {
getProducts()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.main())
.subscribe(
{
// onNext()
_state.value = ProductListFetched(products = it)
},
{
// onError()
_state.value = ErrorFetchingInfo
}
).addTo(compositeDisposable)
}
}
sealed class ProductDetailViewState {
data class ProductListFetched(
val products: List<Product>
): ProductDetailViewState()
object ErrorFetchingInfo : ProductDetailViewState()
}
One thing I'm leaving out it is the adaptation of List<ApiProductResponse>> to List<Product> but that can be handled by mapping the list with a helper function.
I have just started using MVVM for the last 2 of my projects. I can share with you my process of dealing with REST APIs in ViewModel. Hope it will help you and others.
Make a Generic Retrofit Executer Class with their callbacks. which will take a retrofit call object and gives you data.
Make a repository for Your particular package or module where you can handle all API request. in my case, I am getting one user by its id from API.
Here is User Repository.
class UserRepository {
#Inject
lateinit var mRetrofit: Retrofit
init {
MainApplication.appComponent!!.inject(this)
}
private val userApi = mRetrofit.create(UserApi::class.java)
fun getUserbyId(id: Int): Single<NetworkResponse<User>> {
return Single.create<NetworkResponse<User>>{
emitter ->
val callbyId = userApi.getUserbyId(id)
GenericReqExecutor(callbyId).executeCallRequest(object : ExecutionListener<User>{
override fun onSuccess(response: User) {
emitter.onSuccess(NetworkResponse(success = true,
response = response
))
}
override fun onApiError(error: NetworkError) {
emitter.onSuccess(NetworkResponse(success = false,
response = User(),
networkError = error
))
}
override fun onFailure(error: Throwable) {
emitter.onError(error)
}
})
}
}
}
Then Use this Repository in your ViewModel. In my case here is my LoginViewModel code.
class LoginViewModel : ViewModel() {
var userRepo = UserRepository()
fun getUserById(id :Int){
var diposable = userRepo.getUserbyId(id).subscribe({
//OnNext
},{
//onError
})
}
}
I hope this approach can help you to reduce some of your boilerplate code.
Thanks
I had the same question when I started using MVVM a while ago. I came up with the following solution, based on Kotlin suspend functions and coroutines:
Change ApiProductRepositoryImpl.fetchProducts() to run synchronously. To do this, change your retrofit interface to return Call<...> and then change the repository implementation to
// error handling omitted for brevity
override fun fetchProducts() = retrofitApi.fetchProducts().execute().body()
Make your use cases implement the following interface:
interface UseCase<InputType, OutputType> {
suspend fun execute(input: InputType): OutputType
}
so your GetProductsUseCase would look like this:
class GetProductsUseCase: UseCase<Unit, List<Product>> {
suspend fun execute(input: Unit): List<Product> = withContext(Dispatchers.IO){
// withContext causes this block to run on a background thread
return#withContext productRepository.fetchProducts()
}
Execute the use case in your ViewModel
launch {
state.value = ProductDetailViewState.ProductListFetched(getProductsUseCase.execute())
}
See https://github.com/snellen/umvvm for more info and examples.

Categories

Resources