We all know that Android development has been very coupled to its framework. That is because we need the Context
or the Activity
to perform several actions. On the last years Android developers have tried to decouple from the framework when using Clean Architecture.
Whenever we use Clean Architecture, we try to wrap every dependency of our project. This way we can replace them easily if we don’t want to depend on them anymore.
For example, when using MVP in our app, we don’t want the navigation between view to be done in the View layer. The problem is navigation can’t be done by the Presenter layer because a presenter should be pure Java/Kotlin and navigation depends on the Android Framework.
On that case, the typical way to do it is creating a collaborator. This is the only component that knows about the Android Framework, and is able to navigate to a new view. By using such collaborator our presenter is decoupled from the Android Framework and no logic is added to our views.
The Android Framework has different ways to invoke actions and obtain a result. One of them is using an Intent
to start a new activity by calling startActivityForResult()
. With that, when an activity completes the action, it returns the information and the class that started the action can get it on the onActivityResult()
method.
There is a problem here: the only classes that are able to get the result are Activity
and Fragment
and they should be the view in our Clean Architecture when we use MVP.
That is not a real problem to us because we have a lot of ways to avoid using startActivityForResult()
and still get the result we wanted. We usually have all the information into our data layer, using a repository to push and pull it. We could start an action that stores some information into our repository and whenever another part of the app needs that information, it would ask the repository for it.
But in some cases, third-party libraries require us to call them through startActivityForResult()
. That is a very common case when we use a third-party library for images or camera manipulation.
In this post, we are going to use Card.IO Paypal’s library as an example. This library can be used to scan and get information from a credit card using our device’s camera. The idea here is to have a collaborator into our presenter that gives us the information of a credit card. Our presenters should be pure Java/Kotlin and we want the presenter’s collaborators to look like pure Java/Kotlin too. We know that at runtime they are going to use whatever Android stuff they need, like opening a new screen that uses the camera SDK and scan the credit card, but this is only an implementation detail. Easy, right?
The first thing that we are going to do is to create an interface that defines a method to start the scan with a callback to receive the card information as a parameter.
It is going to be used by our presenter in the following way:
So far so good: we have an abstraction that is going to perform the action of getting the information of the card. Now we need to implement it using the Card.IO library. On the Card.IO documentation they show us how to use it, and it is simple: we have to start a new activity for result with an Intent
in which we ask the information that we would like to receive.
Our implementation of CardScanner
needs be able to start that activity and gets the result by itself. The only way to do it on Android is from an activity or fragment. We don’t want to have a new screen just to do that. We could do it with a worker fragment without it interacting with our view.
Our Android implementation is going to receive a FragmentManager
as a constructor parameter. With it, we are able to create as many fragments as we want. As we need to start an activity belonging to the Card.IO library and receive the information of the card, we are going to create a fragment that does just that.
Cool. We have a fragment that is able to start an activity for result and gets the card information from the library. But how are we going to get that information into our Android implementation of the CardScanner
?
The Android SDK has a class that is used to send information through Inter-Process Communication (IPC), called ResultReceiver
.
But, on the last API update, they have limited its use, and some methods are now only allowed to be called from the framework. They have added a Lint Warning, and we don’t want to have @SuppressWarnings
annotations into our code.
This is why I have created another class ResultReceiver
that has the same implementation, allowing us to send information through IPC. It implements Parcelable
, so we are able to pass it to our fragment through a bundle, and notify the result whenever we receive the card information.
We need to receive that in our AndroidCardScanner
and send the result through the callback that we stored.
At the full implementation that you can check in the KleanActivityForResult Repository, the collaborator can be called multiple times. That is the reason why whenever scan()
get called, a new identifier that associates the callback and the fragment is created. Then, when we receive the result, we free the instances.
Perhaps it is not useful on this example, because the user is not going to be able to open it several times. But we could use such approach with an Android service to perform a different action, and in that case, the user is able to call it several times.
And last but not less, this approach allows us to test our app using unit testing. Since we don’t have any Android dependency, we can use jUnit to write black-box tests to check our presentation logic.
First, we mocking our CardScanner
collaborator to simulate that returns us the card information. After that, we verify the information is rendered properly into or view. The view is going to be an spy which allows us to know the interactions made by our presenter.
I have used Kluent library on the tests. It is a “Fluent Assertions” library written specifically for Kotlin. On the next post I will show you its syntactic sugar to write tests and where is the magic.
Don’t forget to check out the sample project
Feel free to add me on Twitter @el_joker333 to discuss anything related (or not even related!) to this post.
Top comments (0)