Yes. Yes, it is :wink:
# android
j
Yes. Yes, it is 😉
d
@jstuyts-squins How do you mock this function in your jvm test code? Thats why i always use util classes and pass them through an interface... is there a better way?
j
Mocking such functions is hard indeed. I don't write a lot of tests, so I have not encountered situations where I would want to mock such a function. And so I cannot advise you on what to do. From a design perspective: If the result of a function heavily influences the behavior of its client, it is probably better to pass a reference to the function (as an interface if you want) to the client. I.e., one of these:
Copy code
class TheFunctionClient(private val isInternetConnectionAvailable: () -> Boolean)
class TheFunctionClient(private val internetConnectionAvailability: InternetConnectionAvailability)
d
@jstuyts-squins Interesting idea... avoids having to write a single member class or a cluttered utility class! I guess you use the application context... how do you manage with dialogs and things that need a more specific context?
j
I am an inject-everything person (this even extends to loggers, because you might want to process logs from different instances differently), because I want to be able to use the full power of object-oriented programming, design patterns, etc. So using global variables (or static singletons like the Android team does, or any other fancy name that hides the simple fact that you are using global variables), is always out of the question. Now, this is hard (way too hard in my opinion), because a lot of the frameworks and libraries make it hard. One of the things that makes proper design hard is type-based dependency injection, where as a client of a type you specify which instance you want using the type (and an optional qualifier) as a key for the implementation of that type. This is the definition of a global variable. Granted, you can have a different global variable in different contexts, but it is still a different global variable. Things get ugly as soon as you introduce another implementation of the type the client wants (introduce qualifier, and modify both the client and the implementation), or when you need to use a client in multiple contexts. So how do I solve that? I use a dependency injection container that does allow me to manage multiple instances of the same type easily. Spring XML supports this without a problem, but I have written my own DI containers that are more flexible and type-safe. When I need a set of objects for a specific context, I simply create a new instance of a DI container that creates the needed objects. Objects from a higher scope are injected into the container, so the child container is not tightly coupled to a specific parent scope. But like I said, this is hard work, but it has always helped me to achieve the flexibility that was promised for object-oriented programming. How would I solve this in, for example, Android? Create an object graph in your own application, when an activity is stared, the activity asks a factory in this object graph to create an object graph for the activity. This is fairly easy. Knowing where to get the factory to create the object graph for other objects managed by Android can be harder.
d
It took me a while to think over what youre saying, i do a lot of testing, so to me di is just a convenient way to replace things with mocks, and keep code dependencies clean according to their responsability. Im not too sure what PRACTICAL gains there are inrolling up custom di's, although i could understand the 'ethical' argument...
j
The practical gain that I get (and I find very important) is that I can freely combine, add, remove and rearrange implementations of an interface without having to write qualifiers, and having to add these qualifiers to both the clients and the implementations of the interfaces. But this apparently is a very uncommon requirement as almost all DI containers use the global-per-context annotations approach
d
Do you have a little concrete example to portray this?
j
Let's say you start with a user repository interface with 1 implementation:
UserRepo
and
JdbcUserRepo
. The view class of the profile page requires an instance of
UserRepo
to get and update information (note: the syntax of the examples below may not be correct):
Copy code
class ProfilePage @Inject constructor (val userRepo: UserRepo) { ... }
Now, you need to add a caching decorator:
Copy code
class CachingUserRepo @Inject constructor (val delegatee: UserRepo) : UserRepo { ... }
Things start to break down at this moment. Most DI containers will start complaining that there are multiple implementations of
UserRepo
. A container is unable to decide which implementation has to be injected into which client. Well, qualifiers to the rescue:
Copy code
@Qualifier @Retention(AnnotationRetention.RUNTIME) annotation class Caching
@Qualifier @Retention(AnnotationRetention.RUNTIME) annotation class Jdbc
And here comes the violation of the open-closed principle. You have to modify existing classes simply because you want to have it talk to another implementation:
Copy code
@Caching
class CachingUserRepo @Inject constructor (@Jdbc val delegatee: UserRepo) : UserRepo { ... }
@Jdbc
class JdbcUserRepo @Inject constructor(...) : UserRepo { ... }
class ProfilePage @Inject constructor (@Caching val userRepo: UserRepo) { ... }
Now imagine you have another decorator that you want to add to 1 context (e.g. production), but not another (e.g. test). Which qualifiers do you have to write? And which classes and which parameters should have which qualifiers?
d
So how would you solve this? By the way, thanks for the effort, I was wondering about this myself simple smile
j
I wrote my own DI container 🙂 It requires explicit wiring using strong-typed references. Here is an example. Let me know if you need help understanding it: https://github.com/squins/ooverkommelig/tree/master/examples/src/main/kotlin/org/ooverkommelig/examples/guice/xml
d
Looks nice! I just find instanciating the graph a bit verbose... Technically without the hard references and compile time checking, kodein could be used to declare multiple graphs like that.. and in Android there's Koin that has those.. I guess each library has pros and cons.. in my case, I need to replace graphs with test ones, and I find kodein s override function very useful... I'm still not clear at what you're gaining design wise.. but there certain advantages that are clear.. what you said before if I understood correctly can be done with kodein..
I still like the idea though simple smile