Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
2adbd93
base branch - initial commit - new project with android studio created
Apr 9, 2019
5cabdba
dependencies implemented - first analysis
Apr 9, 2019
364c931
not needed android test default folder deleted
Apr 9, 2019
59bd124
Koin for DI added
Apr 9, 2019
dc18e54
Library added - constraint layout
Apr 9, 2019
cf6190b
Rx Dependencies
Apr 9, 2019
0dab715
Koin dependency for viewmodel
Apr 9, 2019
0b0d458
added -ktx prefix tu rxjava2 paging library
Apr 10, 2019
f4c7889
RxKotlin and recyclerview libraries
Apr 11, 2019
89eb009
glide dependencies added
Apr 11, 2019
c528794
card view library
Apr 12, 2019
24639a5
TDD - First steps
Apr 9, 2019
147e742
Api - Get Cocktails Preview
Apr 9, 2019
8456482
To Rebase with the previus commit
Apr 9, 2019
c4c6870
Room database implemented
Apr 10, 2019
99577cd
General Improvements
Apr 10, 2019
ec7af61
RecyclerView first steps
Apr 11, 2019
e81c6cd
paging library integrated with recyclerview is working
Apr 11, 2019
551cde7
mappers implementation and tests
Apr 11, 2019
6e1a07e
drink thumb drawed on the screen with glide
Apr 11, 2019
87dded3
UI improvements
Apr 11, 2019
197b5de
cocktail first steps
Apr 12, 2019
72dd987
cocktails developed without UI
Apr 12, 2019
7352032
viewmodels and cocktail improvements
Apr 12, 2019
b9c760b
UI and colors
Apr 12, 2019
fce67c2
improve UI with card views
Apr 12, 2019
5322057
cocktail view with measures, ingredients and instructions
Apr 13, 2019
d3e51e6
loading added to cocktail view
Apr 13, 2019
495bbe8
little refactor about cocktail
Apr 14, 2019
2b2f74c
Inject Mappers
Apr 14, 2019
51f883a
README updated
Apr 14, 2019
8acc564
scrollview for cockatil
Apr 14, 2019
5c75d06
cocktails test
Apr 14, 2019
d8606d9
bonus included - search use case
Apr 14, 2019
f68e16c
resilency
Apr 15, 2019
5a9be94
disposables
Apr 15, 2019
36267d7
clean code
Apr 15, 2019
5e1aa0b
safe click listener implemented
Apr 15, 2019
abfdf94
Last Commit
Apr 15, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
35 changes: 35 additions & 0 deletions .idea/codeStyles/Project.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions .idea/codeStyles/codeStyleConfig.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 18 additions & 0 deletions .idea/gradle.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions .idea/runConfigurations.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/vcs.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,26 @@ Please clone the repository, complete the exercise, and submit a PR for us to re

A) Describe the strategy used to consume the API endpoints and the data management.

I used retrofit to consume the API and ROOM for save the responses locally. Also I used mappers to manage the data
between cloud, database and domain.

B) Explain which library was used for the routing and why. Would you use the same for a consumer facing app targeting thousands of users? Why?

I used activities and Intents for routing, but I will use fragments if a more flexible UI that could support tablets is needed. Also,
The navigation library from Jetpack is pretty interesting applications.
Anyway, the three options are viable for apps with thousands of users.

C) Have you used any strategy to optimize the performance of the list generated for the first feature?

I used the paging library for improve the performance of the list rendered in the first feature.

D) Would you like to add any further comments or observations?

* Tested with mockk
* With Clean Architecture in mind
* MVVM for the presentation layer
* Clean Code


## Overview:

Expand Down
1 change: 1 addition & 0 deletions app/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
65 changes: 65 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'

android {
compileSdkVersion 28
defaultConfig {
applicationId "com.granitosdearena.matiaslev.cocktails"
minSdkVersion 21
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}

dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])

implementation 'io.reactivex.rxjava2:rxkotlin:2.3.0'

// All - Modules
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'org.koin:koin-core:2.0.0-rc-2'
implementation 'org.koin:koin-android-viewmodel:2.0.0-rc-2'

// All - Testing
testImplementation 'junit:junit:4.12'
testImplementation "io.mockk:mockk:1.9"

// Presentation
def lifecycle_version = "2.0.0"

implementation 'androidx.appcompat:appcompat:1.0.0-beta01'
implementation 'androidx.core:core-ktx:1.1.0-alpha05'
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'
implementation 'androidx.recyclerview:recyclerview:1.0.0'
implementation 'com.github.bumptech.glide:glide:4.9.0'
kapt 'com.github.bumptech.glide:compiler:4.9.0'
implementation 'androidx.cardview:cardview:1.0.0'

// Data
def room_version = "2.1.0-alpha06"
def paging_version = "2.1.0"

implementation 'com.squareup.retrofit2:retrofit:2.5.0'
implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.4.0'

implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version" // For Kotlin use kapt instead of annotationProcessor
implementation "androidx.room:room-rxjava2:$room_version"

implementation "androidx.paging:paging-runtime-ktx:$paging_version" // For Kotlin use paging-runtime-ktx
implementation "androidx.paging:paging-rxjava2-ktx:$paging_version" // For Kotlin use paging-rxjava2-ktx
}
21 changes: 21 additions & 0 deletions app/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
28 changes: 28 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.granitosdearena.matiaslev.cocktails">

<uses-permission android:name="android.permission.INTERNET"/>

<application
android:name=".CocktailsApp"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme"
tools:ignore="GoogleAppIndexingWarning">
<activity android:name=".presentation.activities.CocktailActivity">
</activity>
<activity android:name=".presentation.activities.CocktailPreviewActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>

<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>

</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.granitosdearena.matiaslev.cocktails

import android.app.Application
import com.granitosdearena.matiaslev.cocktails.presentation.appModule
import org.koin.android.ext.koin.androidContext
import org.koin.android.ext.koin.androidLogger
import org.koin.core.context.startKoin

class CocktailsApp: Application() {
override fun onCreate() {
super.onCreate()
startKoin {
androidLogger()
androidContext(this@CocktailsApp)
modules(appModule)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package com.granitosdearena.matiaslev.cocktails.data

import android.util.Log
import androidx.paging.PagedList
import androidx.paging.toObservable
import com.granitosdearena.matiaslev.cocktails.data.cloud.CocktailsApi
import com.granitosdearena.matiaslev.cocktails.data.database.AppDatabase
import com.granitosdearena.matiaslev.cocktails.data.mappers.cocktail.CocktailCloudToDatabaseMapper
import com.granitosdearena.matiaslev.cocktails.data.mappers.cocktail.ToCocktailFromDatabaseMapper
import com.granitosdearena.matiaslev.cocktails.data.mappers.cocktailPreview.CocktailPreviewCloudToDatabaseMapper
import com.granitosdearena.matiaslev.cocktails.data.mappers.cocktailPreview.ToCocktailPreviewFromDatabaseMapper
import com.granitosdearena.matiaslev.cocktails.domain.Cocktail
import com.granitosdearena.matiaslev.cocktails.domain.CocktailPreview
import com.granitosdearena.matiaslev.cocktails.domain.CocktailsRepository
import com.granitosdearena.matiaslev.cocktails.presentation.viewModels.CocktailPreviewViewModel
import io.reactivex.Observable
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.rxkotlin.subscribeBy
import io.reactivex.schedulers.Schedulers

class CocktailsRepositoryImpl(val cocktailsApi: CocktailsApi,
val database: AppDatabase,
val disposables: CompositeDisposable,
val cocktailPreviewCloudToDatabaseMapper: CocktailPreviewCloudToDatabaseMapper,
val cocktailCloudToDatabaseMapper: CocktailCloudToDatabaseMapper,
val toCocktailPreviewFromDatabaseMapper: ToCocktailPreviewFromDatabaseMapper,
val toCocktailFromDatabaseMapper: ToCocktailFromDatabaseMapper): CocktailsRepository {

override fun syncCockailsPreview(): Observable<PagedList<CocktailPreview>> {
val disposable = cocktailsApi.getCockailsPreview()
.subscribeOn(Schedulers.io())
.doOnError { Log.d(CocktailPreviewViewModel::class.java.canonicalName, it.message) }
.map { database.cocktailPreviewDao().insertAll(cocktailPreviewCloudToDatabaseMapper.transform(it)) }
.subscribeBy(
onError = { it.printStackTrace() },
onSuccess = { }
)
disposables.add(disposable)

return database.cocktailPreviewDao().getAll()
.map { toCocktailPreviewFromDatabaseMapper.transform(it) }
.toObservable(10)
}

override fun searchCocktailsPreviewByName(name: String): Observable<PagedList<CocktailPreview>> =
database.cocktailPreviewDao().searchCocktailPreviewByName("%$name%")
.map { toCocktailPreviewFromDatabaseMapper.transform(it) }
.toObservable(10)

override fun getCocktail(drinkId: String): Observable<Cocktail> {
val disposable = cocktailsApi.getCocktail(drinkId)
.subscribeOn(Schedulers.io())
.doOnError { Log.d(CocktailPreviewViewModel::class.java.canonicalName, it.message) }
.filter { it.drinks.isNotEmpty() }
.map { database.cocktailDao().insert(cocktailCloudToDatabaseMapper.transform(it.drinks.first())) }
.subscribeBy(
onError = { it.printStackTrace() },
onSuccess = { }
)
disposables.add(disposable)

return database.cocktailDao().searchCocktailById(drinkId.toInt())
.map { toCocktailFromDatabaseMapper.transform(it) }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.granitosdearena.matiaslev.cocktails.data.cloud

import com.granitosdearena.matiaslev.cocktails.data.cloud.model.CocktailCloudList
import com.granitosdearena.matiaslev.cocktails.data.cloud.model.CocktailPreviewCloudList
import io.reactivex.Single
import retrofit2.http.GET
import retrofit2.http.Query

interface CocktailsApi {

@GET("filter.php?g=Cocktail_glass")
fun getCockailsPreview(): Single<CocktailPreviewCloudList>

@GET("lookup.php?")
fun getCocktail(@Query("i") id: String): Single<CocktailCloudList>

}
Loading