Android Repository Implementation with Interface - android

Usually, I use an Interface for my repositories:
interface UserRepository {
fun add()
fun get(userId): User
.....
}
I have three types of Repositories (InMemory, Database, and Network):
They all implement UserRepository:
UserInMemoryRepository - UserDatabaseRepository - UserNetworkRepository
But my network doesn't have any add user API
What is the best practice here?
Remove my UserRepository interface
Leave an empty add user method
...... (Any better approach)

Leaving add() empty doesn't goes in pair with SOLID principles.
If more than one class should override some method, then you shouldn't remove interface either.
What I would do is create interface:
interface Repository {
fun get(userId): User
}
...
interface UserRepository : Repository {
fun add()
}
Then your "Network" can implement Repository, and the rest of classes implement UserRepository.
Of course naming of interfaces is up to you, but according to 'I' in SOLID it is better to have more dedicated interfaces rather than one multifunctional

Related

Injecting Interface to an App Activity - Hilt+Android

New to DI. Let's say there is an interface XYZ in a module ABC, which is being used by the main app project as a dependency. I want to inject that interface XYZ to the MainActivity in the main project. Please see below how I am trying.
ABC Module Contents
XYZ
interface XYZ {
fun init()
}
TestView class implementing interface
class TestView: XYZ {
override fun init(){
}
}
Main project contents
AppModule class
#Module
#InstallIn(SingletonComponent::class)
object AppModule {
#Provides
fun xyz(): XYZ = TestView()
}
MainActivity
#AndroidEntryPoint
class MainActivity : AppCompactActivity() {
#Inject lateinit var xyz : XYZ
override onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
xyz.init()
}
}
Please let me know if there is something wrong with this.
If I use the same interface for another class let's say TestView2 and use it in another activity in main project. Can I provide that view as well in the AppModule class? If yes How will I differentiate it from the first one as Both will be using the same interface?
Thanks in advance.
I'm not a senior dev, so take my words with a grain of salt ;)
Please let me know if there is something wrong with this.
Yes and no (see below)
It will work and some people prefer to provide an interface this way,
however it is better to use #Binds (it generates less code, which makes your app smaller and the build times are quicker)
you can find how to use it here
If I use the same interface for another class let's say TestView2 and use it in another activity in main project. Can I provide that view as well in the AppModule class? If yes How will I differentiate it from the first one as Both will be using the same interface?
If you create 2 provide methods which return the same type, dagger won't know which method to use to provide your dependency, that's why you can name your providers (using the #Named annotation), you can find more about it here
(also, just a comment: Using multiple activities in one application isn't really recommended anymore, and I'm personally against it)

How can a method from an interface can be called in the given project? (Whether I'm wrong?)

This is the project I'm trying to understand. I'm trying to understand what goes after what in this project. What confuses me, is that getFeatures() call in ViewModel. It seems that it calls an abstract function in the interface which is implemented in the file DefaultMapsRepository. I don't understand how it works. I thought that call should be of function from DefaultMapsRepository class where getFeatures() is implemented. So, as I understand, getFeatures() call in ViewModel calls the not implemented method from the interface and then that interface somehow finds the implementation of it and that override fun getFeatures() code runs its body. Correct me if I'm wrong. But If I create another implementation of getFeatures() how would that interface choose which implementation to use? I heard somewhere that interface of repository makes code easier to test but it's hard to understand how if I don't know how this all code works. I sometimes like to test how code works in the console to make code look simpler but I can't replicate the same situation because I'm not able to run the code if I add something to fun main() constructor. I think such structure of a project is used a lot and I want to understand it very well.
A different way of explaining it that might help. Suppose you have this interface and class.
interface Pet {
fun sayName()
}
class Dog(val name: String): Pet {
override fun sayName() {
println(name)
}
}
If some function asks for a Pet, you can pass it an instance of anything that implements the Pet interface.
fun sayPetName(pet: Pet) {
pet.sayName()
}
From the function's point of view, it doesn't have to know what class was passed to it. It just knows that whatever was passed to it is an instance of a Pet, and must therefore have a non-abstract sayName() function.
Even though the function sayName() is abstract inside the definition of the interface, it would be impossible to create an instance of a class where there is an abstract function. There's no such thing as an abstract function in an instance of a class. You can define abstract classes that have abstract functions, but you cannot create instances of them.
You could pass a Dog instance to this function since it qualifies as a Pet. When the function calls pet.sayName(), it is up to the instance of Dog to respond. The function itself doesn't have to know anything about what class type was passed to it.
In the same way, if you have an instance of a Pet that was passed to your constructor, your class can use the sayName() function on it. If you pass the class a Dog instance, even though the class is storing it in a property that is marked Pet and the class doesn't know it has a Dog, if it calls sayName() on it, the Dog instance will use its implementation of sayName().
class SomeClass(val pet: Pet) {
fun sayThePetName() {
pet.sayName()
}
}
If a Dog were passed to the constructor of the above class, it does not get "downgraded" into a Pet interface that has an abstract function. It's still a Dog instance even if that specific type is not exposed to the class in this scope. The code inside the class doesn't know or care that it's a Dog. It just knows it has a reference to some actual class instance that has the functions defined by the Pet interface.
An interface is just a contract. If CoolInterface has a property someValue: Int and a method doThing(): Boolean, then anything that implements that interface is guaranteed to have that property and that method.
The MapsViewModel class takes a MapsRepository parameter, and MapsRepository is just an interface with one function:
suspend fun getFeatures(): Resource<FeaturesResponse>
You can pass in anything that implements that interface, which DefaultMapsRepository does:
class DefaultMapsRepository #Inject constructor(
...
) : MapsRepository {
Since it implements that interface, it needs to implement that getFeatures function, which it does:
// override is the keyword that shows you're not just declaring a new function,
// it's implementing something abstract in a superclass/interface (or overriding
// an open function)
override suspend fun getFeatures(): Resource<FeaturesResponse> {
// bla bla
}
So you can pass in anything at all as your repository, so long as it implements MapsRepository, meaning you need some implementation of that getFeatures function. It doesn't "decide" a version to use, it calls it on whatever you pass in. So you can test it with whatever mock class or object you like
val mockRepo = object : MapsRepository {
override suspend fun getFeatures(): Resource<FeaturesResponse> {
return Resource.Error("oh snap")
}
}
val viewModel = MapsViewModel(mockRepo, whateverTheOtherThingIs)
// calls getFeatures() on the repo
viewModel.getFeatures()
If none of that makes any sense, you should read up on what interfaces and polymorphism are!

Why does the author define a blank interface in a project?

The following code is from the project https://github.com/skydoves/Pokedex
I can't understand why the author need define a blank interface Repository.
What are the benefit using a blank interface Repository ?
Repository.kt
/** Repository is an interface for configuring base repository classes. */
interface Repository
DetailRepository.kt
class DetailRepository #Inject constructor(
private val pokedexClient: PokedexClient,
private val pokemonInfoDao: PokemonInfoDao
) : Repository {
...
}
MainRepository.kt
class MainRepository #Inject constructor(
private val pokedexClient: PokedexClient,
private val pokemonDao: PokemonDao
) : Repository {
...
}
It is called Marker interface pattern. It is interface without any methods or constants. Usually interfaces like this are created to provide special behaviour for a marked classes. You can find a few interfaces like this in java. For example Cloneable and Serializable.
In case of Pokedex i think the reason is much simpler. It looks like this interface was created just to group all repositories. Repository abstraction is never used in the project. Author is always using specific implementations. Repository interface is redundant but can be useful when we want to find all repositories in the project :)
I believe it's because of generics. If you want to create/use an object with generics, the interface will help you a lot.
Consider the following:
interface Animal
class Dog(name:String): Animal{
fun bark(){
println("woff")
}
}
class Cat(name:String): Animal{
fun meow(){
println("meow")
}
}
class Car(name:String){
fun horn(){
println("beep")
}
}
class Farm{
private val animals = ArrayList<Animals>()
fun addAnimal(animal: Animal){
// Even if you don't need a direct function from animal
// you can still store only animals and not cars!
animals.add(animal)
}
}
class Main{
fun main(){
var farm = Farm()
farm.addAnimal(Dog("rex")) // OK
farm.addAnimal(Cat("luna")) // OK
farm.addAnimal(Car("bentley")) // ERROR
}
}
Like this, you can enforce the developer to use the tool as you thought of it, avoiding populating objects with general-purpose items.
A further thought could be if you wanted to add some functionalities and you see at some point, that your class repository actually needs a function for each implementation. If you already have an interface, you won't have to worry to search for all possible implementations (maybe the implementation is in one project extending yours), as the compiler will tell you what is missing.

Dao in Usecase. MVVM or Clean Architecture anti-pattern?

In our "SearchUsecase" we have access to "ShowFtsDao" directly.
Does it violate the Clean Architecture principles? Does it violate the MVVM architecture?
Assuming our intention is to develop a well-built, standard structure, is there anything wrong with this piece of code?
class SearchUsecase #Inject constructor(
private val searchRepository: SearchRepository,
private val showFtsDao: ShowFtsDao,
private val dispatchers: AppCoroutineDispatchers
) : SuspendingWorkInteractor<SearchShows.Params, List<ShowDetailed>>() {
override suspend fun doWork(params: Params): List<ShowDetailed> {
return withContext(dispatchers.io) {
val remoteResults = searchRepository.search(params.query)
if (remoteResults.isNotEmpty()) {
remoteResults
} else {
when {
params.query.isNotBlank() -> showFtsDao.search("*$params.query*")
else -> emptyList()
}
}
}
}
data class Params(val query: String)
}
I believe your use case handles more logic than it needs to.
As a simple explanation I like to think about the components this way:
Sources: RemoteSource (networking), LocalSource (db), optionally MemorySource are an abstraction over your database and networking api and they do the IO thread switching & data mapping (which comes in handy on big projects, where the backend is not exactly mobile driven)
Repository: communicates with the sources, he is responsible for deciding where do you get the data from. I believe in your case if the RemoteSource returns empty data, then you get it from the LocalSource. (you can expose of course different methods like get() or fetch(), where the consumer specifies if it wants the latest data and based on that the repository calls the correct Source.
UseCases: Talk with multiple repositories and combine their data.
Yes, it does.
Because your domain module has access to the data module and actually you've violated the dependency rule.
That rule specifies that something declared in an outer circle must
not be mentioned in the code by an inner circle.
Domain layer must contain interfaces for details (Repositories) which are implemented in the data layer,
and then, they could be injected into the UseCases (DIP).

Inject into arbitrary Logic class that is not instanciated by Hilt

I'm currently migrating an app from anko-sqlite to room as anko has been discontinued a long time ago. In the process of doing so I introduced a repository layer and thought I would try to introduce dependency injection to get references to the repository instances in all my classes.
Prior to this I used a singleton instance that I just shoved into my application classes companion object and accessed that from everywhere.
I managed to inject my repositories into Fragments, Workmanager and Viewmodels. But I do now struggle a bit to understand how they forsaw we should handle this with arbitrary logic classes.
For instance my workmanager worker calls a class that instantiates a list of "jobs" and those need access to the repository. The worker itself actually bearly even does need access to the repositories as all the work is abstracted away from it.
How can I make something like this work
class Job(val someExtraArgINeed: Int) {
#Inject
lateinit var someRepository: SomeRepository // <= this should be injected when the job is instanciated with the constructor that already exists
}
For Activities and so forth we have special annotations #AndroidEntryPoint that makes this work. However, the documentation makes it unclear as to how we should get our instances from any other class that isn't an Android class. I understand that constructor injection is the recommended thing to use. But I do not think it would work here for me without an even more massive refactor required than this already would be.
If I understand your question correctly you can use hilt entry points like this
class CustomClass(applicationContext: Context) {
#EntryPoint
#InstallIn([/*hilt component that provide SomeRepository*/ApplicationComponent]::class)
interface SomeRepositoryEntryPoint {
fun provideSomeRepository(): SomeRepository
}
private val someRepository by lazy {
EntryPointAccessors.fromApplication(applicationContext, SomeRepositoryEntryPoint::class.java).provideSomeRepository()
}
fun doSomething() {
someRepository.doSomething()
}
}

Categories

Resources