SlideShare a Scribd company logo
1 of 74
TDAD
TEST DRIVEN ANDROID DEVELOPMENT
Me
The Goal
Write Android applications
that will not turn into monsters
in the next few months (years).
The actual goal
Write an Android application to display first
generation of Pokemon in a Grid.
What’s TDD
Why TDD
Customer/Goal-Centric
Faster FeedbackHigher Acceptance
Avoid Scope Creep
Modulation
What’s Clean Architecture
“The ratio of time spent reading
(code) versus writing is well over
10 to 1 … (therefore) making it
easy to read makes it easier to
write.”
“Write code for your
mates not for the
machine.”
But which (real) architecture?
Model-View-Presenter
Model-View-ViewModel Viper
Model-View-Adapter
MVP
Model View
Presenter
Why MVP
Android development is hard.
Why MVP
No architecture enforced by the platform,
no MVC (which sucks anyways) like iOS.
Real MVP Architecture
UI 

Activity/
view
Presenter Interactor Repository
API
DB
Android Plain Java/Kotlin
Real MVP Architecture
UI 

Activity/
view
Presenter Interactor Repository
API
DB
Android Plain Java/Kotlin
Background Thread
Real MVP Architecture
UI 

Activity/
view
Presenter Interactor Repository
API
DB
Plain Java/Kotlin
Background Thread
Espresso Unit Test
Android
Try to not re-invent the wheel
Retrofit
Dagger2
Android-priority-queue v2
EventBus
Try to not re-invent the wheel
Retrofit
Dagger2
Android-priority-queue v2
EventBus
http request
dependency injection
job scheduler
communication
Why Kotlin
Can be used with existing Java frameworks and libraries
Costs nothing to adoptCompiles to JVM bytecode
No runtime overhead
Streams & lambdas
Because Lucio is in the room and can’t stand
Java for too long
The actual goal
Write an Android application to display first
generation of Pokemon in a Grid.
The actual goal
Write an Android application to display first
generation of Pokemon in a Grid.
MVP Architecture
UI 

Activity/
view
Presenter Interactor Repository
API
DB
Plain Java/Kotlin
Background Thread
Espresso Unit Test
Android
http://pokeapi.co/api/v2/pokemon/
interface PokemonService {

@GET("api/v2/pokemon")

fun listPokemon(@Query("offset") offset: String = "0"): Call<ListPokemonResponse>

}
class ListPokemonResponse(val count : Int, val results: List<PokemonResponse>)
data class PokemonResponse(val url: String = "", val name: String)
NO TESTS HERE
class PokemonRestClientTest {



lateinit var underTest: PokemonRestClient


@Before

fun setUp() {

underTest = PokemonRestClientImpl()

}



@Test

fun `shouldCallRetrofitClient_andReturnResponse`() {

}

}
class PokemonRestClientTest {



lateinit var underTest: PokemonRestClient
@Mock

lateinit var pokemons: ListPokemonResponse


@Before

fun setUp() {

underTest = PokemonRestClientImpl()

}



@Test

fun `shouldCallRetrofitClient_andReturnResponse`() {

val result = underTest.listPokemon()
assertEquals(pokemons, result)
}

}


class PokemonRestClientTest {



lateinit var underTest: PokemonRestClient



@Mock

lateinit var service: PokemonService



@Mock

lateinit var call: Call<ListPokemonResponse>



@Mock

lateinit var response: Response<ListPokemonResponse>



@Mock

lateinit var pokemons: ListPokemonResponse



@Before

fun setUp() {

Mockito.`when`(service.listPokemon()).thenReturn(call)

Mockito.`when`(call.execute()).thenReturn(response)

Mockito.`when`(response.body()).thenReturn(pokemons)

underTest = PokemonRestClientImpl(service)

}



@Test

fun `shouldCallRetrofitClient_andReturnResponse`() {

val result = underTest.listPokemon()



verify(service).listPokemon()

assertEquals(pokemons, result)

verifyNoMoreInteractions(service)

}

}


interface PokemonRestClient {

fun listPokemon(offset: String = "0"): ListPokemonResponse

}


interface PokemonRestClient {

fun listPokemon(offset: String = "0"): ListPokemonResponse

}
class PokemonRestClientImpl(val pokemonService: PokemonService) : PokemonRestClient {


override fun listPokemon(offset: String): ListPokemonResponse {

return pokemonService.listPokemon(offset).execute().body()

}


}
MVP Architecture
UI 

Activity/
view
Presenter Interactor Repository
API
DB
Plain Java/Kotlin
Background Thread
Espresso Unit Test
Android
class PokemonRepositoryTest {



lateinit var underTest: PokemonRepository





@Before

fun setUp() {

underTest = PokemonRepositoryImpl()

}



@Test

fun `shouldCallServiceListPokemon`() {
}

}
class PokemonRepositoryTest {



lateinit var underTest: PokemonRepository



@Mock

lateinit var client: PokemonRestClient



@Before

fun setUp() {

underTest = PokemonRepositoryImpl(client)

}



@Test

fun `shouldCallServiceListPokemon`() {
underTest.getPokemon()

verify(client).listPokemon()

verifyNoMoreInteractions(client)

}

}
interface PokemonRepository {

fun getPokemon()

}
class PokemonRepositoryImpl(val client: PokemonClient) : PokemonRepository{


override fun getPokemon(){

client.listPokemon()

}


}
class PokemonRepositoryTest {



val SQUIRTLE = "Squirtle"

val CHARMENDER = "Charmender"

val NUMBER_OF_POKEMONS = 2



lateinit var underTest: PokemonRepository



@Mock

lateinit var client: PokemonClient



@Before

fun setUp() {

underTest = PokemonRepositoryImpl(client)

}



@Test

fun `shouldReturnPokemonsFromNetwork`() {

val squirtle = PokemonResponse(name = SQUIRTLE)

val charmender = PokemonResponse(name = CHARMENDER)

val pokemonsResponse = ListPokemonResponse(NUMBER_OF_POKEMONS, listOf(squirtle, charmender))

Mockito.`when`(client.listPokemon()).thenReturn(pokemonsResponse)



val result = underTest.getPokemon()



assertNotNull(result)

assertEquals(NUMBER_OF_POKEMONS, result.size)

assertEquals(SQUIRTLE, result[0].name)

assertEquals(CHARMENDER, result[1].name)

}

}
class PokemonRepositoryTest {



val SQUIRTLE = "Squirtle"

val CHARMENDER = "Charmender"

val NUMBER_OF_POKEMONS = 2



@Rule @JvmField var mockitoRule = MockitoJUnit.rule()



lateinit var underTest: PokemonRepository



@Mock

lateinit var client: PokemonRestClient



@Before

fun setUp() {

val squirtle = PokemonResponse(name = SQUIRTLE)

val charmender = PokemonResponse(name = CHARMENDER)

val pokemonsResponse = ListPokemonResponse(NUMBER_OF_POKEMONS, listOf(squirtle, charmender))

Mockito.`when`(client.listPokemon()).thenReturn(pokemonsResponse)

underTest = PokemonRepositoryImpl(client)

}



@Test

fun `shouldCallPokemonRestClient`() {

underTest.getPokemon()



verify(client).listPokemon()

verifyNoMoreInteractions(client)

}



@Test

fun `shouldReturnPokemonsFromNetwork`() {

val result = underTest.getPokemon()



assertNotNull(result)

assertEquals(NUMBER_OF_POKEMONS, result.size)

assertEquals(SQUIRTLE, result[0].name)

assertEquals(CHARMENDER, result[1].name)

}

}
Those are Pokemon objects not
PokemonResponse objects
data class Pokemon(val name:String)
data class Pokemon(val name:String)
public class PokemonJava {



private final String name;



public PokemonJava(String name) {

this.name = name;

}



public String getName() {

return name;

}



@Override

public boolean equals(Object o) {

if (this == o) return true;

if (o == null || getClass() != o.getClass()) return false;



PokemonJava that = (PokemonJava) o;



return name != null ? name.equals(that.name) : that.name == null;



}



@Override

public int hashCode() {

return name != null ? name.hashCode() : 0;

}



@Override

public String toString() {

return "PokemonJava{" +

"name='" + name + ''' +

'}';

}

}
1 line vs 26 lines
class PokemonRepositoryImpl(val client: PokemonRestClient) : PokemonRepository {

override fun getPokemon(): List<Pokemon> {

return client.listPokemon().results.map { Pokemon(it.name) }

}

}
class PokemonRepositoryImpl(val client: PokemonRestClient) : PokemonRepository {

override fun getPokemon(): List<Pokemon> {

return client.listPokemon().results.map { Pokemon(it.name) }

}

}
public class PokemonRepositoryImplJava implements PokemonRepository {



private final PokemonRestClient client;



public PokemonRepositoryImplJava(PokemonRestClient client) {

this.client = client;

}



@NotNull

@Override

public List<Pokemon> getPokemon() {

try {

ListPokemonResponse listPokemonResponse = client.listPokemon().getResults();

List<Pokemon> result = new ArrayList<>(listPokemonResponse.getCount());

for (PokemonResponse pokemonResponse : listPokemonResponse.getResults()){

result.add(new Pokemon(pokemonResponse.getName()));

}

return result;

} catch (IOException e) {

e.printStackTrace();

}

return null;

}

}
5 lines vs 21 lines
MVP Architecture
UI 

Activity/
view
Presenter Interactor Repository
API
DB
Plain Java/Kotlin
Background Thread
Espresso Unit Test
Android
class GetPokemonJobTest {



lateinit var underTest: GetPokemonJob



@Mock

lateinit var params: Params



@Mock

lateinit var bus: EventBus



@Mock

lateinit var repository: PokemonRepository



@Before

fun setUp() {

underTest = GetPokemonJob(params, repository, bus)

}



@Test

fun `shouldCallRepository_andPostEventOnBus`() {

underTest.onRun()



verify(repository).getPokemon()

}
}
class GetPokemonJobTest {



@Rule @JvmField var mockitoRule = MockitoJUnit.rule()



lateinit var underTest: GetPokemonJob



@Mock

lateinit var params: Params



@Mock

lateinit var bus: EventBus



@Mock

lateinit var repository: PokemonRepository



val finishedEventCaptor: ArgumentCaptor<GetPokemonJob.FinishedEvent> =
ArgumentCaptor.forClass(GetPokemonJob.FinishedEvent::class.java)

val list = arrayListOf(Pokemon("Squirtle"), Pokemon("Bulbasaur"))



@Before

fun setUp() {

Mockito.`when`(repository.getPokemon()).thenReturn(list)

underTest = GetPokemonJob(params, repository, bus)

}



@Test

fun `shouldCallRepository_andPostEventOnBus`() {

underTest.onRun()



verify(bus).postSticky(finishedEventCaptor.capture())

assertTrue(finishedEventCaptor.value.success)

assertEquals(finishedEventCaptor.value.pokemon, list)



verify(repository).getPokemon()

verifyNoMoreInteractions(repository, bus)

}
}
class GetPokemonJob(val params: Params?, val repository: PokemonRepository,
val bus: EventBus) : Job(params) {



override fun shouldReRunOnThrowable(throwable: Throwable, runCount: Int,
maxRunCount: Int): RetryConstraint {

}



override fun onAdded() {

}



override fun onCancel(cancelReason: Int, throwable: Throwable?) {

}



override fun onRun() {

}



}
class GetPokemonJob(val params: Params?, val repository: PokemonRepository,
val bus: EventBus) : Job(params) {



override fun shouldReRunOnThrowable(throwable: Throwable,
runCount: Int, maxRunCount: Int): RetryConstraint {

return RetryConstraint(false)

}



override fun onAdded() {

}



override fun onCancel(cancelReason: Int, throwable: Throwable?) {

}



override fun onRun() {

val pokemon = repository.getPokemon()

bus.postSticky(FinishedEvent(true, pokemon))

}



open class FinishedEvent(val success: Boolean,
open val pokemon: List<Pokemon>)



}
MVP Architecture
UI 

Activity/
view
Presenter Interactor Repository
API
DB
Plain Java/Kotlin
Background Thread
Espresso Unit Test
Android
The actual goal
Write an Android application to display first
generation of Pokemon in a Grid.
class PokemonInteractorImplTest {



@Rule @JvmField var mockitoRule = MockitoJUnit.rule()



lateinit var underTest: PokemonInteractor



@Mock

lateinit var jobManager: JobManager



@Mock

lateinit var repository: PokemonRepository



@Mock

lateinit var bus: EventBus



var jobCaptor: ArgumentCaptor<GetPokemonJob> =
ArgumentCaptor.forClass(GetPokemonJob::class.java)



@Before

fun setUp() {

underTest = PokemonInteractorImpl(jobManager, repository, bus)

}



@Test

fun `shouldLaunchGetPokemonJob`() {

underTest.requestPokemon()



verify(jobManager).addJobInBackground(jobCaptor.capture())

assertTrue(jobCaptor.value is GetPokemonJob)

assertEquals(1, jobCaptor.value.params?.priority)

assertTrue(jobCaptor.value.params?.isNetworkRequired!!)

verifyNoMoreInteractions(jobManager)



}

}
interface PokemonInteractor {

fun requestPokemon()

}
class PokemonInteractorImpl(val jobManager: JobManager,

val repository: PokemonRepository,

val bus: EventBus) : PokemonInteractor {

override fun requestPokemon() {

val params = Params(1).requireNetwork()

jobManager.addJobInBackground(GetPokemonJob(params, repository, bus))

}

}
MVP Architecture
UI 

Activity/
view
Presenter Interactor Repository
API
DB
Plain Java/Kotlin
Background Thread
Espresso Unit Test
Android
class PokemonPresenterImplTest {



lateinit var underTest: PokemonPresenter



@Mock

lateinit var view: PokemonView



@Mock

lateinit var interactor: PokemonInteractor



@Before

fun setUp() {

underTest = PokemonPresenterImpl(interactor)

underTest.init(view)

}



@Test

fun `shouldRequestPokemon`() {

underTest.onStart()



verify(interactor).requestPokemon()

verify(view).showLoading()

verifyNoMoreInteractions(interactor, view)

}



}
interface PokemonView{

fun showLoading()

}
interface PokemonPresenter {

fun init(view: PokemonView)



fun onStart()

}
class PokemonPresenterImpl(val interactor: PokemonInteractor,
val bus: EventBus) : PokemonPresenter {



lateinit var view: PokemonView



override fun init(view: PokemonView) {

this.view = view

}



override fun onStart() {

interactor.requestPokemon()

view.showLoading()

}

}

class PokemonPresenterImplTest {



lateinit var underTest: PokemonPresenter



@Mock

lateinit var view: PokemonView



@Mock

lateinit var event: GetPokemonJob.FinishedEvent



@Mock

lateinit var interactor: PokemonInteractor



@Mock

lateinit var bus: EventBus



var list = arrayListOf(Pokemon("Squirtle"), Pokemon("Bulbasaur"))



@Before

fun setUp() {

underTest = PokemonPresenterImpl(interactor, bus)

underTest.init(view)

}



@Test

fun `shouldShowPokemon_andRemoveEventFromBus`() {

Mockito.`when`(event.pokemon).thenReturn(list)

underTest.onPokemonRetrieved(event)



verify(view).hideLoading()

verify(view).pokemonLoaded(event.pokemon)

verify(bus).removeStickyEvent(event)
verifyNoMoreInteractions(bus, view)

}

}
interface PokemonView{

fun showLoading()

fun hideLoading()

fun pokemonLoaded(pokemons: List<Pokemon>)

}
interface PokemonPresenter {

fun init(view: PokemonView)

fun onStart()

fun onPokemonRetrieved(event: GetPokemonJob.FinishedEvent)

}
class PokemonPresenterImpl(val interactor: PokemonInteractor,
val bus: EventBus) : PokemonPresenter {



lateinit var view: PokemonView



override fun init(view: PokemonView) {

this.view = view

}



override fun onStart() {

interactor.requestPokemon()

view.showLoading()

}



@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)

override fun onPokemonRetrieved(event: GetPokemonJob.FinishedEvent) {

bus.removeStickyEvent(event)

view.hideLoading()

view.pokemonLoaded(event.pokemon)

}

}

class PokemonPresenterImplTest {



@Rule @JvmField var mockitoRule = MockitoJUnit.rule()



lateinit var underTest: PokemonPresenter



@Mock

lateinit var view: PokemonView



@Mock

lateinit var event: GetPokemonJob.FinishedEvent



@Mock

lateinit var interactor: PokemonInteractor



@Mock

lateinit var bus: EventBus



var list = arrayListOf(Pokemon("Squirtle"), Pokemon("Bulbasaur"))



@Before

fun setUp() {

Mockito.`when`(event.pokemon).thenReturn(list)

underTest = PokemonPresenterImpl(interactor, bus)

underTest.init(view)

}



@Test

fun `shouldRequestPokemon`() {

underTest.onStart()



verify(interactor).requestPokemon()

verify(view).showLoading()

verifyNoMoreInteractions(interactor, view)

}



@Test

fun `shouldShowPokemon_andRemoveEventFromBus`() {

underTest.onPokemonRetrieved(event)



verify(view).hideLoading()

verify(view).pokemonLoaded(event.pokemon)

verify(bus).removeStickyEvent(event)

verifyNoMoreInteractions(bus, view)

}

}
MVP Architecture
UI 

Activity/
view
Presenter Interactor Repository
API
DB
Plain Java/Kotlin
Background Thread
Espresso Unit Test
Android
Is TDD with espresso
possible?
Is TDD with espresso
possible?
YES.
Do you want to do it?
Do you want to do it?
No.
Do you want to do it?
No.
Slow Feedback
Device dependent
Too many uses cases at once
Requires an emulator or a device
UI
Do manual test and specific espresso
tests for core features.
Dagger
Dependency Injection
From Google
Avoid new keyword
Component in isolation
class PokemonRepositoryImpl(val client: PokemonRestClient) : PokemonRepository {



override fun getPokemon(): List<Pokemon> {

return client.listPokemon().results.map { Pokemon(it.name) }

}

}
class PokemonRepositoryImpl(val client: PokemonRestClient) : PokemonRepository {



override fun getPokemon(): List<Pokemon> {

return client.listPokemon().results.map { Pokemon(it.name) }

}

}
class PokemonRepositoryImpl : PokemonRepository {



override fun getPokemon(): List<Pokemon> {
val client = PokemonRestClientImpl()

return client.listPokemon().results.map { Pokemon(it.name) }

}

}
class PokemonRepositoryImpl(val client: PokemonRestClient) : PokemonRepository {



override fun getPokemon(): List<Pokemon> {

return client.listPokemon().results.map { Pokemon(it.name) }

}

}
class PokemonRepositoryImpl : PokemonRepository {



override fun getPokemon(): List<Pokemon> {
val client = PokemonRestClientImpl()

return client.listPokemon().results.map { Pokemon(it.name) }

}

}
class PokemonRestClientImpl(val pokemonService: PokemonService) : PokemonRestClient {

override fun listPokemon(offset: String): ListPokemonResponse {

return pokemonService.listPokemon(offset).execute().body()

}

}
class PokemonRepositoryImpl(val client: PokemonRestClient) : PokemonRepository {



override fun getPokemon(): List<Pokemon> {

return client.listPokemon().results.map { Pokemon(it.name) }

}

}
class PokemonRepositoryImpl : PokemonRepository {



override fun getPokemon(): List<Pokemon> {
val service = PokemonService()
val client = PokemonRestClientImpl(service)

return client.listPokemon().results.map { Pokemon(it.name) }

}

}
class PokemonRepositoryImpl(val client: PokemonRestClient) : PokemonRepository {



override fun getPokemon(): List<Pokemon> {

return client.listPokemon().results.map { Pokemon(it.name) }

}

}
class PokemonRepositoryImpl : PokemonRepository {



override fun getPokemon(): List<Pokemon> {
val service = Retrofit.Builder()

.client(client)

.baseUrl("http://pokeapi.co/")

.addConverterFactory(GsonConverterFactory.create())

.build()
val client = PokemonRestClientImpl(service)

return client.listPokemon().results.map { Pokemon(it.name) }

}

}
class PokemonRepositoryImpl(val client: PokemonRestClient) : PokemonRepository {



override fun getPokemon(): List<Pokemon> {

return client.listPokemon().results.map { Pokemon(it.name) }

}

}
class PokemonRepositoryImpl : PokemonRepository {



override fun getPokemon(): List<Pokemon> {
val service = Retrofit.Builder()

.client(client)

.baseUrl("http://pokeapi.co/")

.addConverterFactory(GsonConverterFactory.create())

.build()
val client = PokemonRestClientImpl(service)

return client.listPokemon().results.map { Pokemon(it.name) }

}

}
0 dependency
3 dependency:
- retrofit
- gson converted
- pokemon rest client imp
@Module

class NetworkModule {



@Provides

@Singleton

fun providesPokemonRestClient(service: PokemonService): PokemonRestClient {

return PokemonRestClientImpl(service)

}



@Provides

@Singleton

fun providesPokemonService(client: OkHttpClient): PokemonService {

val retrofit = Retrofit.Builder()

.client(client)

.baseUrl("http://pokeapi.co/")

.addConverterFactory(GsonConverterFactory.create())

.build()



return retrofit.create(PokemonService::class.java)

}



@Provides

@Singleton

internal fun providesOkHttpClient(): OkHttpClient {

return OkHttpClient.Builder()

.addInterceptor(HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY))

.build()

}

}
@Module

class RepositoryModule {



@Provides

fun providesPokemonRepository(client: PokemonRestClient): PokemonRepository {

return PokemonRepositoryImpl(client)

}

}
@Module

class InteractorModule {



@Provides

@Singleton

fun providesJobManager(context: Context): JobManager {

val builder = Configuration.Builder(context)

.customLogger(JobLogger())

return JobManager(builder.build())

}



@Provides

fun providesPokemonInteractor(bus: EventBus, jobManager: JobManager, repository: PokemonRepository):
PokemonInteractor {

return PokemonInteractorImpl(jobManager, repository, bus)

}

}
@Module

class PresenterModule{



@Provides

fun providesPokemonPresenter(bus: EventBus, interactor: PokemonInteractor) : PokemonPresenter{

return PokemonPresenterImpl(interactor, bus)

}

}
class PokemonListActivity : AppCompatActivity(), PokemonView {



@Inject

lateinit var presenter: PokemonPresenter



lateinit var loader: ProgressBar

lateinit var grid: RecyclerView



override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)

setContentView(R.layout.activity_main)

loader = findViewById(R.id.loader) as ProgressBar

grid = findViewById(R.id.grid) as RecyclerView


val uiComponent = DaggerUIComponent.builder()
.appComponent((application as PokemonApp).appComponent).build()

uiComponent.inject(this)

presenter.init(this)

}



override fun onStart() {

super.onStart()

presenter.onStart()

}



override fun showLoading() {

loader.visibility = View.VISIBLE

grid.visibility = View.INVISIBLE

}



override fun hideLoading() {

loader.visibility = View.INVISIBLE

grid.visibility = View.VISIBLE

}



override fun pokemonLoaded(pokemons: List<Pokemon>) {

grid.adapter = PokemonAdapter(pokemons)

}

}
Run
The actual goal
Write an Android application to display first
generation of Pokemon in a Grid.
Outcome
Fully tested!
Half tested
Library
~80%
Repository
https://github.com/dbottillo/TDDAndroid
Thanks.
twitter.com/dbottillo
medium.com/@dbottillo

More Related Content

What's hot

Functional Programming with Groovy
Functional Programming with GroovyFunctional Programming with Groovy
Functional Programming with GroovyArturo Herrero
 
The Kotlin Programming Language
The Kotlin Programming LanguageThe Kotlin Programming Language
The Kotlin Programming Languageintelliyole
 
DRYing to Monad in Java8
DRYing to Monad in Java8DRYing to Monad in Java8
DRYing to Monad in Java8Dhaval Dalal
 
Advanced Java Practical File
Advanced Java Practical FileAdvanced Java Practical File
Advanced Java Practical FileSoumya Behera
 
Lambda and Stream Master class - part 1
Lambda and Stream Master class - part 1Lambda and Stream Master class - part 1
Lambda and Stream Master class - part 1José Paumard
 
The Sincerest Form of Flattery
The Sincerest Form of FlatteryThe Sincerest Form of Flattery
The Sincerest Form of FlatteryJosé Paumard
 
The Ring programming language version 1.4 book - Part 11 of 30
The Ring programming language version 1.4 book - Part 11 of 30The Ring programming language version 1.4 book - Part 11 of 30
The Ring programming language version 1.4 book - Part 11 of 30Mahmoud Samir Fayed
 
Code generation for alternative languages
Code generation for alternative languagesCode generation for alternative languages
Code generation for alternative languagesRafael Winterhalter
 
Kotlin Developer Starter in Android projects
Kotlin Developer Starter in Android projectsKotlin Developer Starter in Android projects
Kotlin Developer Starter in Android projectsBartosz Kosarzycki
 
Making Java more dynamic: runtime code generation for the JVM
Making Java more dynamic: runtime code generation for the JVMMaking Java more dynamic: runtime code generation for the JVM
Making Java more dynamic: runtime code generation for the JVMRafael Winterhalter
 
Object Design - Part 1
Object Design - Part 1Object Design - Part 1
Object Design - Part 1Dhaval Dalal
 
Lambdas and Streams Master Class Part 2
Lambdas and Streams Master Class Part 2Lambdas and Streams Master Class Part 2
Lambdas and Streams Master Class Part 2José Paumard
 
The Ring programming language version 1.5.4 book - Part 39 of 185
The Ring programming language version 1.5.4 book - Part 39 of 185The Ring programming language version 1.5.4 book - Part 39 of 185
The Ring programming language version 1.5.4 book - Part 39 of 185Mahmoud Samir Fayed
 
Mirah Talk for Boulder Ruby Group
Mirah Talk for Boulder Ruby GroupMirah Talk for Boulder Ruby Group
Mirah Talk for Boulder Ruby Groupbaroquebobcat
 
Kotlin as a Better Java
Kotlin as a Better JavaKotlin as a Better Java
Kotlin as a Better JavaGarth Gilmour
 

What's hot (20)

Clean code slide
Clean code slideClean code slide
Clean code slide
 
Polyglot JVM
Polyglot JVMPolyglot JVM
Polyglot JVM
 
Functional Programming with Groovy
Functional Programming with GroovyFunctional Programming with Groovy
Functional Programming with Groovy
 
Lambda Functions in Java 8
Lambda Functions in Java 8Lambda Functions in Java 8
Lambda Functions in Java 8
 
The Kotlin Programming Language
The Kotlin Programming LanguageThe Kotlin Programming Language
The Kotlin Programming Language
 
DRYing to Monad in Java8
DRYing to Monad in Java8DRYing to Monad in Java8
DRYing to Monad in Java8
 
Advanced Java Practical File
Advanced Java Practical FileAdvanced Java Practical File
Advanced Java Practical File
 
Lambda and Stream Master class - part 1
Lambda and Stream Master class - part 1Lambda and Stream Master class - part 1
Lambda and Stream Master class - part 1
 
The Sincerest Form of Flattery
The Sincerest Form of FlatteryThe Sincerest Form of Flattery
The Sincerest Form of Flattery
 
The Ring programming language version 1.4 book - Part 11 of 30
The Ring programming language version 1.4 book - Part 11 of 30The Ring programming language version 1.4 book - Part 11 of 30
The Ring programming language version 1.4 book - Part 11 of 30
 
Code generation for alternative languages
Code generation for alternative languagesCode generation for alternative languages
Code generation for alternative languages
 
The Joy Of Ruby
The Joy Of RubyThe Joy Of Ruby
The Joy Of Ruby
 
Kotlin Developer Starter in Android projects
Kotlin Developer Starter in Android projectsKotlin Developer Starter in Android projects
Kotlin Developer Starter in Android projects
 
Java 5 and 6 New Features
Java 5 and 6 New FeaturesJava 5 and 6 New Features
Java 5 and 6 New Features
 
Making Java more dynamic: runtime code generation for the JVM
Making Java more dynamic: runtime code generation for the JVMMaking Java more dynamic: runtime code generation for the JVM
Making Java more dynamic: runtime code generation for the JVM
 
Object Design - Part 1
Object Design - Part 1Object Design - Part 1
Object Design - Part 1
 
Lambdas and Streams Master Class Part 2
Lambdas and Streams Master Class Part 2Lambdas and Streams Master Class Part 2
Lambdas and Streams Master Class Part 2
 
The Ring programming language version 1.5.4 book - Part 39 of 185
The Ring programming language version 1.5.4 book - Part 39 of 185The Ring programming language version 1.5.4 book - Part 39 of 185
The Ring programming language version 1.5.4 book - Part 39 of 185
 
Mirah Talk for Boulder Ruby Group
Mirah Talk for Boulder Ruby GroupMirah Talk for Boulder Ruby Group
Mirah Talk for Boulder Ruby Group
 
Kotlin as a Better Java
Kotlin as a Better JavaKotlin as a Better Java
Kotlin as a Better Java
 

Viewers also liked

Viewers also liked (8)

3er grado examen fina
3er grado   examen fina3er grado   examen fina
3er grado examen fina
 
Full CV_Pham Kien Cuong_VN
Full CV_Pham Kien Cuong_VNFull CV_Pham Kien Cuong_VN
Full CV_Pham Kien Cuong_VN
 
CECL Methodology Series for C&I Loan Pools
CECL Methodology Series for C&I Loan PoolsCECL Methodology Series for C&I Loan Pools
CECL Methodology Series for C&I Loan Pools
 
Presentazione ANORC Professioni
Presentazione ANORC Professioni Presentazione ANORC Professioni
Presentazione ANORC Professioni
 
чергування у в ,і-й. презентація
чергування у в ,і-й. презентаціячергування у в ,і-й. презентація
чергування у в ,і-й. презентація
 
eSett corporate presentation
eSett corporate presentationeSett corporate presentation
eSett corporate presentation
 
Wireless led notice board
Wireless led notice boardWireless led notice board
Wireless led notice board
 
Intervención familiar desde la educación infantil gladys
Intervención familiar desde la educación infantil gladysIntervención familiar desde la educación infantil gladys
Intervención familiar desde la educación infantil gladys
 

Similar to TDAD

Painless Persistence with Realm
Painless Persistence with RealmPainless Persistence with Realm
Painless Persistence with RealmChristian Melchior
 
A swift introduction to Swift
A swift introduction to SwiftA swift introduction to Swift
A swift introduction to SwiftGiordano Scalzo
 
Ast transformations
Ast transformationsAst transformations
Ast transformationsHamletDRC
 
The Future of JVM Languages
The Future of JVM Languages The Future of JVM Languages
The Future of JVM Languages VictorSzoltysek
 
Kotlin : Advanced Tricks - Ubiratan Soares
Kotlin : Advanced Tricks - Ubiratan SoaresKotlin : Advanced Tricks - Ubiratan Soares
Kotlin : Advanced Tricks - Ubiratan SoaresiMasters
 
つくってあそぼ Kotlin DSL ~拡張編~
つくってあそぼ Kotlin DSL ~拡張編~つくってあそぼ Kotlin DSL ~拡張編~
つくってあそぼ Kotlin DSL ~拡張編~kamedon39
 
Scala in practice
Scala in practiceScala in practice
Scala in practicepatforna
 
Is your C# optimized
Is your C# optimizedIs your C# optimized
Is your C# optimizedWoody Pewitt
 
Davide Cerbo - Kotlin: forse è la volta buona - Codemotion Milan 2017
Davide Cerbo - Kotlin: forse è la volta buona - Codemotion Milan 2017 Davide Cerbo - Kotlin: forse è la volta buona - Codemotion Milan 2017
Davide Cerbo - Kotlin: forse è la volta buona - Codemotion Milan 2017 Codemotion
 
Designing with Groovy Traits - Gr8Conf India
Designing with Groovy Traits - Gr8Conf IndiaDesigning with Groovy Traits - Gr8Conf India
Designing with Groovy Traits - Gr8Conf IndiaNaresha K
 
What can be done with Java, but should better be done with Erlang (@pavlobaron)
What can be done with Java, but should better be done with Erlang (@pavlobaron)What can be done with Java, but should better be done with Erlang (@pavlobaron)
What can be done with Java, but should better be done with Erlang (@pavlobaron)Pavlo Baron
 
Применение паттерна Page Object для автоматизации веб сервисов - новый взгляд
Применение паттерна Page Object для автоматизации веб сервисов - новый взглядПрименение паттерна Page Object для автоматизации веб сервисов - новый взгляд
Применение паттерна Page Object для автоматизации веб сервисов - новый взглядCOMAQA.BY
 
Android & Kotlin - The code awakens #03
Android & Kotlin - The code awakens #03Android & Kotlin - The code awakens #03
Android & Kotlin - The code awakens #03Omar Miatello
 
AST Transformations at JFokus
AST Transformations at JFokusAST Transformations at JFokus
AST Transformations at JFokusHamletDRC
 
Extreme Swift
Extreme SwiftExtreme Swift
Extreme SwiftMovel
 
create-netflix-clone-02-server.pdf
create-netflix-clone-02-server.pdfcreate-netflix-clone-02-server.pdf
create-netflix-clone-02-server.pdfShaiAlmog1
 

Similar to TDAD (20)

Painless Persistence with Realm
Painless Persistence with RealmPainless Persistence with Realm
Painless Persistence with Realm
 
Pooya Khaloo Presentation on IWMC 2015
Pooya Khaloo Presentation on IWMC 2015Pooya Khaloo Presentation on IWMC 2015
Pooya Khaloo Presentation on IWMC 2015
 
A swift introduction to Swift
A swift introduction to SwiftA swift introduction to Swift
A swift introduction to Swift
 
Ast transformations
Ast transformationsAst transformations
Ast transformations
 
The Future of JVM Languages
The Future of JVM Languages The Future of JVM Languages
The Future of JVM Languages
 
Kotlin : Advanced Tricks - Ubiratan Soares
Kotlin : Advanced Tricks - Ubiratan SoaresKotlin : Advanced Tricks - Ubiratan Soares
Kotlin : Advanced Tricks - Ubiratan Soares
 
つくってあそぼ Kotlin DSL ~拡張編~
つくってあそぼ Kotlin DSL ~拡張編~つくってあそぼ Kotlin DSL ~拡張編~
つくってあそぼ Kotlin DSL ~拡張編~
 
Scala in practice
Scala in practiceScala in practice
Scala in practice
 
Is your C# optimized
Is your C# optimizedIs your C# optimized
Is your C# optimized
 
Davide Cerbo - Kotlin: forse è la volta buona - Codemotion Milan 2017
Davide Cerbo - Kotlin: forse è la volta buona - Codemotion Milan 2017 Davide Cerbo - Kotlin: forse è la volta buona - Codemotion Milan 2017
Davide Cerbo - Kotlin: forse è la volta buona - Codemotion Milan 2017
 
Designing with Groovy Traits - Gr8Conf India
Designing with Groovy Traits - Gr8Conf IndiaDesigning with Groovy Traits - Gr8Conf India
Designing with Groovy Traits - Gr8Conf India
 
What can be done with Java, but should better be done with Erlang (@pavlobaron)
What can be done with Java, but should better be done with Erlang (@pavlobaron)What can be done with Java, but should better be done with Erlang (@pavlobaron)
What can be done with Java, but should better be done with Erlang (@pavlobaron)
 
Применение паттерна Page Object для автоматизации веб сервисов - новый взгляд
Применение паттерна Page Object для автоматизации веб сервисов - новый взглядПрименение паттерна Page Object для автоматизации веб сервисов - новый взгляд
Применение паттерна Page Object для автоматизации веб сервисов - новый взгляд
 
Android & Kotlin - The code awakens #03
Android & Kotlin - The code awakens #03Android & Kotlin - The code awakens #03
Android & Kotlin - The code awakens #03
 
AST Transformations at JFokus
AST Transformations at JFokusAST Transformations at JFokus
AST Transformations at JFokus
 
Scala taxonomy
Scala taxonomyScala taxonomy
Scala taxonomy
 
Scala introduction
Scala introductionScala introduction
Scala introduction
 
Extreme Swift
Extreme SwiftExtreme Swift
Extreme Swift
 
create-netflix-clone-02-server.pdf
create-netflix-clone-02-server.pdfcreate-netflix-clone-02-server.pdf
create-netflix-clone-02-server.pdf
 
Using the Windows 8 Runtime from C++
Using the Windows 8 Runtime from C++Using the Windows 8 Runtime from C++
Using the Windows 8 Runtime from C++
 

Recently uploaded

Balasore Best It Company|| Top 10 IT Company || Balasore Software company Odisha
Balasore Best It Company|| Top 10 IT Company || Balasore Software company OdishaBalasore Best It Company|| Top 10 IT Company || Balasore Software company Odisha
Balasore Best It Company|| Top 10 IT Company || Balasore Software company Odishasmiwainfosol
 
CRM Contender Series: HubSpot vs. Salesforce
CRM Contender Series: HubSpot vs. SalesforceCRM Contender Series: HubSpot vs. Salesforce
CRM Contender Series: HubSpot vs. SalesforceBrainSell Technologies
 
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptx
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptxKnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptx
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptxTier1 app
 
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASE
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASEBATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASE
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASEOrtus Solutions, Corp
 
SpotFlow: Tracking Method Calls and States at Runtime
SpotFlow: Tracking Method Calls and States at RuntimeSpotFlow: Tracking Method Calls and States at Runtime
SpotFlow: Tracking Method Calls and States at Runtimeandrehoraa
 
ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...
ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...
ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...Christina Lin
 
Recruitment Management Software Benefits (Infographic)
Recruitment Management Software Benefits (Infographic)Recruitment Management Software Benefits (Infographic)
Recruitment Management Software Benefits (Infographic)Hr365.us smith
 
Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...
Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...
Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...Matt Ray
 
Call Us🔝>༒+91-9711147426⇛Call In girls karol bagh (Delhi)
Call Us🔝>༒+91-9711147426⇛Call In girls karol bagh (Delhi)Call Us🔝>༒+91-9711147426⇛Call In girls karol bagh (Delhi)
Call Us🔝>༒+91-9711147426⇛Call In girls karol bagh (Delhi)jennyeacort
 
Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...
Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...
Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...soniya singh
 
Unveiling Design Patterns: A Visual Guide with UML Diagrams
Unveiling Design Patterns: A Visual Guide with UML DiagramsUnveiling Design Patterns: A Visual Guide with UML Diagrams
Unveiling Design Patterns: A Visual Guide with UML DiagramsAhmed Mohamed
 
What is Fashion PLM and Why Do You Need It
What is Fashion PLM and Why Do You Need ItWhat is Fashion PLM and Why Do You Need It
What is Fashion PLM and Why Do You Need ItWave PLM
 
What are the key points to focus on before starting to learn ETL Development....
What are the key points to focus on before starting to learn ETL Development....What are the key points to focus on before starting to learn ETL Development....
What are the key points to focus on before starting to learn ETL Development....kzayra69
 
Software Project Health Check: Best Practices and Techniques for Your Product...
Software Project Health Check: Best Practices and Techniques for Your Product...Software Project Health Check: Best Practices and Techniques for Your Product...
Software Project Health Check: Best Practices and Techniques for Your Product...Velvetech LLC
 
EY_Graph Database Powered Sustainability
EY_Graph Database Powered SustainabilityEY_Graph Database Powered Sustainability
EY_Graph Database Powered SustainabilityNeo4j
 
Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...
Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...
Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...OnePlan Solutions
 
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...stazi3110
 
Cloud Data Center Network Construction - IEEE
Cloud Data Center Network Construction - IEEECloud Data Center Network Construction - IEEE
Cloud Data Center Network Construction - IEEEVICTOR MAESTRE RAMIREZ
 
Intelligent Home Wi-Fi Solutions | ThinkPalm
Intelligent Home Wi-Fi Solutions | ThinkPalmIntelligent Home Wi-Fi Solutions | ThinkPalm
Intelligent Home Wi-Fi Solutions | ThinkPalmSujith Sukumaran
 

Recently uploaded (20)

Balasore Best It Company|| Top 10 IT Company || Balasore Software company Odisha
Balasore Best It Company|| Top 10 IT Company || Balasore Software company OdishaBalasore Best It Company|| Top 10 IT Company || Balasore Software company Odisha
Balasore Best It Company|| Top 10 IT Company || Balasore Software company Odisha
 
CRM Contender Series: HubSpot vs. Salesforce
CRM Contender Series: HubSpot vs. SalesforceCRM Contender Series: HubSpot vs. Salesforce
CRM Contender Series: HubSpot vs. Salesforce
 
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptx
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptxKnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptx
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptx
 
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASE
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASEBATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASE
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASE
 
SpotFlow: Tracking Method Calls and States at Runtime
SpotFlow: Tracking Method Calls and States at RuntimeSpotFlow: Tracking Method Calls and States at Runtime
SpotFlow: Tracking Method Calls and States at Runtime
 
ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...
ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...
ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...
 
Recruitment Management Software Benefits (Infographic)
Recruitment Management Software Benefits (Infographic)Recruitment Management Software Benefits (Infographic)
Recruitment Management Software Benefits (Infographic)
 
Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...
Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...
Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...
 
Call Us🔝>༒+91-9711147426⇛Call In girls karol bagh (Delhi)
Call Us🔝>༒+91-9711147426⇛Call In girls karol bagh (Delhi)Call Us🔝>༒+91-9711147426⇛Call In girls karol bagh (Delhi)
Call Us🔝>༒+91-9711147426⇛Call In girls karol bagh (Delhi)
 
Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...
Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...
Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...
 
Unveiling Design Patterns: A Visual Guide with UML Diagrams
Unveiling Design Patterns: A Visual Guide with UML DiagramsUnveiling Design Patterns: A Visual Guide with UML Diagrams
Unveiling Design Patterns: A Visual Guide with UML Diagrams
 
What is Fashion PLM and Why Do You Need It
What is Fashion PLM and Why Do You Need ItWhat is Fashion PLM and Why Do You Need It
What is Fashion PLM and Why Do You Need It
 
Hot Sexy call girls in Patel Nagar🔝 9953056974 🔝 escort Service
Hot Sexy call girls in Patel Nagar🔝 9953056974 🔝 escort ServiceHot Sexy call girls in Patel Nagar🔝 9953056974 🔝 escort Service
Hot Sexy call girls in Patel Nagar🔝 9953056974 🔝 escort Service
 
What are the key points to focus on before starting to learn ETL Development....
What are the key points to focus on before starting to learn ETL Development....What are the key points to focus on before starting to learn ETL Development....
What are the key points to focus on before starting to learn ETL Development....
 
Software Project Health Check: Best Practices and Techniques for Your Product...
Software Project Health Check: Best Practices and Techniques for Your Product...Software Project Health Check: Best Practices and Techniques for Your Product...
Software Project Health Check: Best Practices and Techniques for Your Product...
 
EY_Graph Database Powered Sustainability
EY_Graph Database Powered SustainabilityEY_Graph Database Powered Sustainability
EY_Graph Database Powered Sustainability
 
Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...
Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...
Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...
 
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...
 
Cloud Data Center Network Construction - IEEE
Cloud Data Center Network Construction - IEEECloud Data Center Network Construction - IEEE
Cloud Data Center Network Construction - IEEE
 
Intelligent Home Wi-Fi Solutions | ThinkPalm
Intelligent Home Wi-Fi Solutions | ThinkPalmIntelligent Home Wi-Fi Solutions | ThinkPalm
Intelligent Home Wi-Fi Solutions | ThinkPalm
 

TDAD

  • 2. Me
  • 3. The Goal Write Android applications that will not turn into monsters in the next few months (years).
  • 4. The actual goal Write an Android application to display first generation of Pokemon in a Grid.
  • 6. Why TDD Customer/Goal-Centric Faster FeedbackHigher Acceptance Avoid Scope Creep Modulation
  • 7. What’s Clean Architecture “The ratio of time spent reading (code) versus writing is well over 10 to 1 … (therefore) making it easy to read makes it easier to write.” “Write code for your mates not for the machine.”
  • 8. But which (real) architecture? Model-View-Presenter Model-View-ViewModel Viper Model-View-Adapter
  • 11. Why MVP No architecture enforced by the platform, no MVC (which sucks anyways) like iOS.
  • 12. Real MVP Architecture UI Activity/ view Presenter Interactor Repository API DB Android Plain Java/Kotlin
  • 13. Real MVP Architecture UI Activity/ view Presenter Interactor Repository API DB Android Plain Java/Kotlin Background Thread
  • 14. Real MVP Architecture UI Activity/ view Presenter Interactor Repository API DB Plain Java/Kotlin Background Thread Espresso Unit Test Android
  • 15. Try to not re-invent the wheel Retrofit Dagger2 Android-priority-queue v2 EventBus
  • 16. Try to not re-invent the wheel Retrofit Dagger2 Android-priority-queue v2 EventBus http request dependency injection job scheduler communication
  • 17. Why Kotlin Can be used with existing Java frameworks and libraries Costs nothing to adoptCompiles to JVM bytecode No runtime overhead Streams & lambdas Because Lucio is in the room and can’t stand Java for too long
  • 18. The actual goal Write an Android application to display first generation of Pokemon in a Grid.
  • 19. The actual goal Write an Android application to display first generation of Pokemon in a Grid.
  • 20. MVP Architecture UI Activity/ view Presenter Interactor Repository API DB Plain Java/Kotlin Background Thread Espresso Unit Test Android
  • 22. interface PokemonService {
 @GET("api/v2/pokemon")
 fun listPokemon(@Query("offset") offset: String = "0"): Call<ListPokemonResponse>
 } class ListPokemonResponse(val count : Int, val results: List<PokemonResponse>) data class PokemonResponse(val url: String = "", val name: String) NO TESTS HERE
  • 23. class PokemonRestClientTest {
 
 lateinit var underTest: PokemonRestClient 
 @Before
 fun setUp() {
 underTest = PokemonRestClientImpl()
 }
 
 @Test
 fun `shouldCallRetrofitClient_andReturnResponse`() {
 }
 }
  • 24. class PokemonRestClientTest {
 
 lateinit var underTest: PokemonRestClient @Mock
 lateinit var pokemons: ListPokemonResponse 
 @Before
 fun setUp() {
 underTest = PokemonRestClientImpl()
 }
 
 @Test
 fun `shouldCallRetrofitClient_andReturnResponse`() {
 val result = underTest.listPokemon() assertEquals(pokemons, result) }
 }
  • 25. 
 class PokemonRestClientTest {
 
 lateinit var underTest: PokemonRestClient
 
 @Mock
 lateinit var service: PokemonService
 
 @Mock
 lateinit var call: Call<ListPokemonResponse>
 
 @Mock
 lateinit var response: Response<ListPokemonResponse>
 
 @Mock
 lateinit var pokemons: ListPokemonResponse
 
 @Before
 fun setUp() {
 Mockito.`when`(service.listPokemon()).thenReturn(call)
 Mockito.`when`(call.execute()).thenReturn(response)
 Mockito.`when`(response.body()).thenReturn(pokemons)
 underTest = PokemonRestClientImpl(service)
 }
 
 @Test
 fun `shouldCallRetrofitClient_andReturnResponse`() {
 val result = underTest.listPokemon()
 
 verify(service).listPokemon()
 assertEquals(pokemons, result)
 verifyNoMoreInteractions(service)
 }
 }
  • 26. 
 interface PokemonRestClient {
 fun listPokemon(offset: String = "0"): ListPokemonResponse
 }
  • 27. 
 interface PokemonRestClient {
 fun listPokemon(offset: String = "0"): ListPokemonResponse
 } class PokemonRestClientImpl(val pokemonService: PokemonService) : PokemonRestClient { 
 override fun listPokemon(offset: String): ListPokemonResponse {
 return pokemonService.listPokemon(offset).execute().body()
 } 
 }
  • 28. MVP Architecture UI Activity/ view Presenter Interactor Repository API DB Plain Java/Kotlin Background Thread Espresso Unit Test Android
  • 29. class PokemonRepositoryTest {
 
 lateinit var underTest: PokemonRepository
 
 
 @Before
 fun setUp() {
 underTest = PokemonRepositoryImpl()
 }
 
 @Test
 fun `shouldCallServiceListPokemon`() { }
 }
  • 30. class PokemonRepositoryTest {
 
 lateinit var underTest: PokemonRepository
 
 @Mock
 lateinit var client: PokemonRestClient
 
 @Before
 fun setUp() {
 underTest = PokemonRepositoryImpl(client)
 }
 
 @Test
 fun `shouldCallServiceListPokemon`() { underTest.getPokemon()
 verify(client).listPokemon()
 verifyNoMoreInteractions(client)
 }
 }
  • 31. interface PokemonRepository {
 fun getPokemon()
 } class PokemonRepositoryImpl(val client: PokemonClient) : PokemonRepository{ 
 override fun getPokemon(){
 client.listPokemon()
 } 
 }
  • 32. class PokemonRepositoryTest {
 
 val SQUIRTLE = "Squirtle"
 val CHARMENDER = "Charmender"
 val NUMBER_OF_POKEMONS = 2
 
 lateinit var underTest: PokemonRepository
 
 @Mock
 lateinit var client: PokemonClient
 
 @Before
 fun setUp() {
 underTest = PokemonRepositoryImpl(client)
 }
 
 @Test
 fun `shouldReturnPokemonsFromNetwork`() {
 val squirtle = PokemonResponse(name = SQUIRTLE)
 val charmender = PokemonResponse(name = CHARMENDER)
 val pokemonsResponse = ListPokemonResponse(NUMBER_OF_POKEMONS, listOf(squirtle, charmender))
 Mockito.`when`(client.listPokemon()).thenReturn(pokemonsResponse)
 
 val result = underTest.getPokemon()
 
 assertNotNull(result)
 assertEquals(NUMBER_OF_POKEMONS, result.size)
 assertEquals(SQUIRTLE, result[0].name)
 assertEquals(CHARMENDER, result[1].name)
 }
 }
  • 33. class PokemonRepositoryTest {
 
 val SQUIRTLE = "Squirtle"
 val CHARMENDER = "Charmender"
 val NUMBER_OF_POKEMONS = 2
 
 @Rule @JvmField var mockitoRule = MockitoJUnit.rule()
 
 lateinit var underTest: PokemonRepository
 
 @Mock
 lateinit var client: PokemonRestClient
 
 @Before
 fun setUp() {
 val squirtle = PokemonResponse(name = SQUIRTLE)
 val charmender = PokemonResponse(name = CHARMENDER)
 val pokemonsResponse = ListPokemonResponse(NUMBER_OF_POKEMONS, listOf(squirtle, charmender))
 Mockito.`when`(client.listPokemon()).thenReturn(pokemonsResponse)
 underTest = PokemonRepositoryImpl(client)
 }
 
 @Test
 fun `shouldCallPokemonRestClient`() {
 underTest.getPokemon()
 
 verify(client).listPokemon()
 verifyNoMoreInteractions(client)
 }
 
 @Test
 fun `shouldReturnPokemonsFromNetwork`() {
 val result = underTest.getPokemon()
 
 assertNotNull(result)
 assertEquals(NUMBER_OF_POKEMONS, result.size)
 assertEquals(SQUIRTLE, result[0].name)
 assertEquals(CHARMENDER, result[1].name)
 }
 } Those are Pokemon objects not PokemonResponse objects
  • 34. data class Pokemon(val name:String)
  • 35. data class Pokemon(val name:String) public class PokemonJava {
 
 private final String name;
 
 public PokemonJava(String name) {
 this.name = name;
 }
 
 public String getName() {
 return name;
 }
 
 @Override
 public boolean equals(Object o) {
 if (this == o) return true;
 if (o == null || getClass() != o.getClass()) return false;
 
 PokemonJava that = (PokemonJava) o;
 
 return name != null ? name.equals(that.name) : that.name == null;
 
 }
 
 @Override
 public int hashCode() {
 return name != null ? name.hashCode() : 0;
 }
 
 @Override
 public String toString() {
 return "PokemonJava{" +
 "name='" + name + ''' +
 '}';
 }
 } 1 line vs 26 lines
  • 36. class PokemonRepositoryImpl(val client: PokemonRestClient) : PokemonRepository {
 override fun getPokemon(): List<Pokemon> {
 return client.listPokemon().results.map { Pokemon(it.name) }
 }
 }
  • 37. class PokemonRepositoryImpl(val client: PokemonRestClient) : PokemonRepository {
 override fun getPokemon(): List<Pokemon> {
 return client.listPokemon().results.map { Pokemon(it.name) }
 }
 } public class PokemonRepositoryImplJava implements PokemonRepository {
 
 private final PokemonRestClient client;
 
 public PokemonRepositoryImplJava(PokemonRestClient client) {
 this.client = client;
 }
 
 @NotNull
 @Override
 public List<Pokemon> getPokemon() {
 try {
 ListPokemonResponse listPokemonResponse = client.listPokemon().getResults();
 List<Pokemon> result = new ArrayList<>(listPokemonResponse.getCount());
 for (PokemonResponse pokemonResponse : listPokemonResponse.getResults()){
 result.add(new Pokemon(pokemonResponse.getName()));
 }
 return result;
 } catch (IOException e) {
 e.printStackTrace();
 }
 return null;
 }
 } 5 lines vs 21 lines
  • 38. MVP Architecture UI Activity/ view Presenter Interactor Repository API DB Plain Java/Kotlin Background Thread Espresso Unit Test Android
  • 39. class GetPokemonJobTest {
 
 lateinit var underTest: GetPokemonJob
 
 @Mock
 lateinit var params: Params
 
 @Mock
 lateinit var bus: EventBus
 
 @Mock
 lateinit var repository: PokemonRepository
 
 @Before
 fun setUp() {
 underTest = GetPokemonJob(params, repository, bus)
 }
 
 @Test
 fun `shouldCallRepository_andPostEventOnBus`() {
 underTest.onRun()
 
 verify(repository).getPokemon()
 } }
  • 40. class GetPokemonJobTest {
 
 @Rule @JvmField var mockitoRule = MockitoJUnit.rule()
 
 lateinit var underTest: GetPokemonJob
 
 @Mock
 lateinit var params: Params
 
 @Mock
 lateinit var bus: EventBus
 
 @Mock
 lateinit var repository: PokemonRepository
 
 val finishedEventCaptor: ArgumentCaptor<GetPokemonJob.FinishedEvent> = ArgumentCaptor.forClass(GetPokemonJob.FinishedEvent::class.java)
 val list = arrayListOf(Pokemon("Squirtle"), Pokemon("Bulbasaur"))
 
 @Before
 fun setUp() {
 Mockito.`when`(repository.getPokemon()).thenReturn(list)
 underTest = GetPokemonJob(params, repository, bus)
 }
 
 @Test
 fun `shouldCallRepository_andPostEventOnBus`() {
 underTest.onRun()
 
 verify(bus).postSticky(finishedEventCaptor.capture())
 assertTrue(finishedEventCaptor.value.success)
 assertEquals(finishedEventCaptor.value.pokemon, list)
 
 verify(repository).getPokemon()
 verifyNoMoreInteractions(repository, bus)
 } }
  • 41. class GetPokemonJob(val params: Params?, val repository: PokemonRepository, val bus: EventBus) : Job(params) {
 
 override fun shouldReRunOnThrowable(throwable: Throwable, runCount: Int, maxRunCount: Int): RetryConstraint {
 }
 
 override fun onAdded() {
 }
 
 override fun onCancel(cancelReason: Int, throwable: Throwable?) {
 }
 
 override fun onRun() {
 }
 
 }
  • 42. class GetPokemonJob(val params: Params?, val repository: PokemonRepository, val bus: EventBus) : Job(params) {
 
 override fun shouldReRunOnThrowable(throwable: Throwable, runCount: Int, maxRunCount: Int): RetryConstraint {
 return RetryConstraint(false)
 }
 
 override fun onAdded() {
 }
 
 override fun onCancel(cancelReason: Int, throwable: Throwable?) {
 }
 
 override fun onRun() {
 val pokemon = repository.getPokemon()
 bus.postSticky(FinishedEvent(true, pokemon))
 }
 
 open class FinishedEvent(val success: Boolean, open val pokemon: List<Pokemon>)
 
 }
  • 43. MVP Architecture UI Activity/ view Presenter Interactor Repository API DB Plain Java/Kotlin Background Thread Espresso Unit Test Android
  • 44. The actual goal Write an Android application to display first generation of Pokemon in a Grid.
  • 45. class PokemonInteractorImplTest {
 
 @Rule @JvmField var mockitoRule = MockitoJUnit.rule()
 
 lateinit var underTest: PokemonInteractor
 
 @Mock
 lateinit var jobManager: JobManager
 
 @Mock
 lateinit var repository: PokemonRepository
 
 @Mock
 lateinit var bus: EventBus
 
 var jobCaptor: ArgumentCaptor<GetPokemonJob> = ArgumentCaptor.forClass(GetPokemonJob::class.java)
 
 @Before
 fun setUp() {
 underTest = PokemonInteractorImpl(jobManager, repository, bus)
 }
 
 @Test
 fun `shouldLaunchGetPokemonJob`() {
 underTest.requestPokemon()
 
 verify(jobManager).addJobInBackground(jobCaptor.capture())
 assertTrue(jobCaptor.value is GetPokemonJob)
 assertEquals(1, jobCaptor.value.params?.priority)
 assertTrue(jobCaptor.value.params?.isNetworkRequired!!)
 verifyNoMoreInteractions(jobManager)
 
 }
 }
  • 46. interface PokemonInteractor {
 fun requestPokemon()
 } class PokemonInteractorImpl(val jobManager: JobManager,
 val repository: PokemonRepository,
 val bus: EventBus) : PokemonInteractor {
 override fun requestPokemon() {
 val params = Params(1).requireNetwork()
 jobManager.addJobInBackground(GetPokemonJob(params, repository, bus))
 }
 }
  • 47. MVP Architecture UI Activity/ view Presenter Interactor Repository API DB Plain Java/Kotlin Background Thread Espresso Unit Test Android
  • 48. class PokemonPresenterImplTest {
 
 lateinit var underTest: PokemonPresenter
 
 @Mock
 lateinit var view: PokemonView
 
 @Mock
 lateinit var interactor: PokemonInteractor
 
 @Before
 fun setUp() {
 underTest = PokemonPresenterImpl(interactor)
 underTest.init(view)
 }
 
 @Test
 fun `shouldRequestPokemon`() {
 underTest.onStart()
 
 verify(interactor).requestPokemon()
 verify(view).showLoading()
 verifyNoMoreInteractions(interactor, view)
 }
 
 }
  • 49. interface PokemonView{
 fun showLoading()
 } interface PokemonPresenter {
 fun init(view: PokemonView)
 
 fun onStart()
 } class PokemonPresenterImpl(val interactor: PokemonInteractor, val bus: EventBus) : PokemonPresenter {
 
 lateinit var view: PokemonView
 
 override fun init(view: PokemonView) {
 this.view = view
 }
 
 override fun onStart() {
 interactor.requestPokemon()
 view.showLoading()
 }
 }

  • 50. class PokemonPresenterImplTest {
 
 lateinit var underTest: PokemonPresenter
 
 @Mock
 lateinit var view: PokemonView
 
 @Mock
 lateinit var event: GetPokemonJob.FinishedEvent
 
 @Mock
 lateinit var interactor: PokemonInteractor
 
 @Mock
 lateinit var bus: EventBus
 
 var list = arrayListOf(Pokemon("Squirtle"), Pokemon("Bulbasaur"))
 
 @Before
 fun setUp() {
 underTest = PokemonPresenterImpl(interactor, bus)
 underTest.init(view)
 }
 
 @Test
 fun `shouldShowPokemon_andRemoveEventFromBus`() {
 Mockito.`when`(event.pokemon).thenReturn(list)
 underTest.onPokemonRetrieved(event)
 
 verify(view).hideLoading()
 verify(view).pokemonLoaded(event.pokemon)
 verify(bus).removeStickyEvent(event) verifyNoMoreInteractions(bus, view)
 }
 }
  • 51. interface PokemonView{
 fun showLoading()
 fun hideLoading()
 fun pokemonLoaded(pokemons: List<Pokemon>)
 } interface PokemonPresenter {
 fun init(view: PokemonView)
 fun onStart()
 fun onPokemonRetrieved(event: GetPokemonJob.FinishedEvent)
 } class PokemonPresenterImpl(val interactor: PokemonInteractor, val bus: EventBus) : PokemonPresenter {
 
 lateinit var view: PokemonView
 
 override fun init(view: PokemonView) {
 this.view = view
 }
 
 override fun onStart() {
 interactor.requestPokemon()
 view.showLoading()
 }
 
 @Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
 override fun onPokemonRetrieved(event: GetPokemonJob.FinishedEvent) {
 bus.removeStickyEvent(event)
 view.hideLoading()
 view.pokemonLoaded(event.pokemon)
 }
 }

  • 52. class PokemonPresenterImplTest {
 
 @Rule @JvmField var mockitoRule = MockitoJUnit.rule()
 
 lateinit var underTest: PokemonPresenter
 
 @Mock
 lateinit var view: PokemonView
 
 @Mock
 lateinit var event: GetPokemonJob.FinishedEvent
 
 @Mock
 lateinit var interactor: PokemonInteractor
 
 @Mock
 lateinit var bus: EventBus
 
 var list = arrayListOf(Pokemon("Squirtle"), Pokemon("Bulbasaur"))
 
 @Before
 fun setUp() {
 Mockito.`when`(event.pokemon).thenReturn(list)
 underTest = PokemonPresenterImpl(interactor, bus)
 underTest.init(view)
 }
 
 @Test
 fun `shouldRequestPokemon`() {
 underTest.onStart()
 
 verify(interactor).requestPokemon()
 verify(view).showLoading()
 verifyNoMoreInteractions(interactor, view)
 }
 
 @Test
 fun `shouldShowPokemon_andRemoveEventFromBus`() {
 underTest.onPokemonRetrieved(event)
 
 verify(view).hideLoading()
 verify(view).pokemonLoaded(event.pokemon)
 verify(bus).removeStickyEvent(event)
 verifyNoMoreInteractions(bus, view)
 }
 }
  • 53. MVP Architecture UI Activity/ view Presenter Interactor Repository API DB Plain Java/Kotlin Background Thread Espresso Unit Test Android
  • 54. Is TDD with espresso possible?
  • 55. Is TDD with espresso possible? YES.
  • 56. Do you want to do it?
  • 57. Do you want to do it? No.
  • 58. Do you want to do it? No. Slow Feedback Device dependent Too many uses cases at once Requires an emulator or a device
  • 59. UI Do manual test and specific espresso tests for core features.
  • 60. Dagger Dependency Injection From Google Avoid new keyword Component in isolation
  • 61. class PokemonRepositoryImpl(val client: PokemonRestClient) : PokemonRepository {
 
 override fun getPokemon(): List<Pokemon> {
 return client.listPokemon().results.map { Pokemon(it.name) }
 }
 }
  • 62. class PokemonRepositoryImpl(val client: PokemonRestClient) : PokemonRepository {
 
 override fun getPokemon(): List<Pokemon> {
 return client.listPokemon().results.map { Pokemon(it.name) }
 }
 } class PokemonRepositoryImpl : PokemonRepository {
 
 override fun getPokemon(): List<Pokemon> { val client = PokemonRestClientImpl()
 return client.listPokemon().results.map { Pokemon(it.name) }
 }
 }
  • 63. class PokemonRepositoryImpl(val client: PokemonRestClient) : PokemonRepository {
 
 override fun getPokemon(): List<Pokemon> {
 return client.listPokemon().results.map { Pokemon(it.name) }
 }
 } class PokemonRepositoryImpl : PokemonRepository {
 
 override fun getPokemon(): List<Pokemon> { val client = PokemonRestClientImpl()
 return client.listPokemon().results.map { Pokemon(it.name) }
 }
 } class PokemonRestClientImpl(val pokemonService: PokemonService) : PokemonRestClient {
 override fun listPokemon(offset: String): ListPokemonResponse {
 return pokemonService.listPokemon(offset).execute().body()
 }
 }
  • 64. class PokemonRepositoryImpl(val client: PokemonRestClient) : PokemonRepository {
 
 override fun getPokemon(): List<Pokemon> {
 return client.listPokemon().results.map { Pokemon(it.name) }
 }
 } class PokemonRepositoryImpl : PokemonRepository {
 
 override fun getPokemon(): List<Pokemon> { val service = PokemonService() val client = PokemonRestClientImpl(service)
 return client.listPokemon().results.map { Pokemon(it.name) }
 }
 }
  • 65. class PokemonRepositoryImpl(val client: PokemonRestClient) : PokemonRepository {
 
 override fun getPokemon(): List<Pokemon> {
 return client.listPokemon().results.map { Pokemon(it.name) }
 }
 } class PokemonRepositoryImpl : PokemonRepository {
 
 override fun getPokemon(): List<Pokemon> { val service = Retrofit.Builder()
 .client(client)
 .baseUrl("http://pokeapi.co/")
 .addConverterFactory(GsonConverterFactory.create())
 .build() val client = PokemonRestClientImpl(service)
 return client.listPokemon().results.map { Pokemon(it.name) }
 }
 }
  • 66. class PokemonRepositoryImpl(val client: PokemonRestClient) : PokemonRepository {
 
 override fun getPokemon(): List<Pokemon> {
 return client.listPokemon().results.map { Pokemon(it.name) }
 }
 } class PokemonRepositoryImpl : PokemonRepository {
 
 override fun getPokemon(): List<Pokemon> { val service = Retrofit.Builder()
 .client(client)
 .baseUrl("http://pokeapi.co/")
 .addConverterFactory(GsonConverterFactory.create())
 .build() val client = PokemonRestClientImpl(service)
 return client.listPokemon().results.map { Pokemon(it.name) }
 }
 } 0 dependency 3 dependency: - retrofit - gson converted - pokemon rest client imp
  • 67. @Module
 class NetworkModule {
 
 @Provides
 @Singleton
 fun providesPokemonRestClient(service: PokemonService): PokemonRestClient {
 return PokemonRestClientImpl(service)
 }
 
 @Provides
 @Singleton
 fun providesPokemonService(client: OkHttpClient): PokemonService {
 val retrofit = Retrofit.Builder()
 .client(client)
 .baseUrl("http://pokeapi.co/")
 .addConverterFactory(GsonConverterFactory.create())
 .build()
 
 return retrofit.create(PokemonService::class.java)
 }
 
 @Provides
 @Singleton
 internal fun providesOkHttpClient(): OkHttpClient {
 return OkHttpClient.Builder()
 .addInterceptor(HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY))
 .build()
 }
 }
  • 68. @Module
 class RepositoryModule {
 
 @Provides
 fun providesPokemonRepository(client: PokemonRestClient): PokemonRepository {
 return PokemonRepositoryImpl(client)
 }
 } @Module
 class InteractorModule {
 
 @Provides
 @Singleton
 fun providesJobManager(context: Context): JobManager {
 val builder = Configuration.Builder(context)
 .customLogger(JobLogger())
 return JobManager(builder.build())
 }
 
 @Provides
 fun providesPokemonInteractor(bus: EventBus, jobManager: JobManager, repository: PokemonRepository): PokemonInteractor {
 return PokemonInteractorImpl(jobManager, repository, bus)
 }
 } @Module
 class PresenterModule{
 
 @Provides
 fun providesPokemonPresenter(bus: EventBus, interactor: PokemonInteractor) : PokemonPresenter{
 return PokemonPresenterImpl(interactor, bus)
 }
 }
  • 69. class PokemonListActivity : AppCompatActivity(), PokemonView {
 
 @Inject
 lateinit var presenter: PokemonPresenter
 
 lateinit var loader: ProgressBar
 lateinit var grid: RecyclerView
 
 override fun onCreate(savedInstanceState: Bundle?) {
 super.onCreate(savedInstanceState)
 setContentView(R.layout.activity_main)
 loader = findViewById(R.id.loader) as ProgressBar
 grid = findViewById(R.id.grid) as RecyclerView 
 val uiComponent = DaggerUIComponent.builder() .appComponent((application as PokemonApp).appComponent).build()
 uiComponent.inject(this)
 presenter.init(this)
 }
 
 override fun onStart() {
 super.onStart()
 presenter.onStart()
 }
 
 override fun showLoading() {
 loader.visibility = View.VISIBLE
 grid.visibility = View.INVISIBLE
 }
 
 override fun hideLoading() {
 loader.visibility = View.INVISIBLE
 grid.visibility = View.VISIBLE
 }
 
 override fun pokemonLoaded(pokemons: List<Pokemon>) {
 grid.adapter = PokemonAdapter(pokemons)
 }
 }
  • 70. Run
  • 71. The actual goal Write an Android application to display first generation of Pokemon in a Grid.