├── .gitignore ├── .idea ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── compiler.xml ├── encodings.xml └── inspectionProfiles │ └── Project_Default.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── kotlinsevilla │ │ └── cookietinder │ │ └── MainActivity.kt │ └── res │ ├── drawable-v24 │ └── ic_launcher_foreground.xml │ ├── drawable-xhdpi │ └── cookie_oreo.png │ ├── drawable │ ├── cookie_placeholder.png │ └── ic_launcher_background.xml │ ├── layout │ └── activity_main.xml │ ├── mipmap-anydpi-v26 │ ├── ic_launcher.xml │ └── ic_launcher_round.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ └── values │ ├── colors.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── screenshots └── app.png └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | ### Gradle ### 2 | # Exclude Folder List # 3 | .gradle/ 4 | build/ 5 | 6 | # built application files 7 | *.apk 8 | *.ap_ 9 | 10 | # files for the dex VM 11 | *.dex 12 | 13 | # Java class files 14 | *.class 15 | 16 | # generated files 17 | bin/ 18 | gen/ 19 | 20 | # Local configuration file (sdk path, etc) 21 | local.properties 22 | 23 | # Log Files 24 | *.log 25 | 26 | # IDEA/Android Studio ignores 27 | *.iml 28 | *.ipr 29 | *.iws 30 | **/.idea/* 31 | 32 | # IDEA/Android Studio Ignore exceptions 33 | !/.idea/copyright/ 34 | !/.idea/fileTemplates/ 35 | !/.idea/inspectionProfiles/ 36 | !/.idea/scopes/ 37 | !/.idea/.name 38 | !/.idea/codeStyles/ 39 | !/.idea/compiler.xml 40 | !/.idea/encodings.xml 41 | 42 | # Captures (heap dumps, layout inspector, etc) 43 | captures/ 44 | 45 | # Windows and macOS metadata 46 | Thumbs.db 47 | *.DS_Store 48 | desktop.ini 49 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 9 | 10 | 14 | 15 | 118 | 119 | 121 | 122 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Workshop: my first Android app with Kotlin 2 | 3 | - We are going to learn the basics of Android development 4 | - We are going to use Kotlin 5 | - We are going to practise unit testing in Android with Kotlin 6 | 7 | --- 8 | 9 | ## Getting started 10 | 11 | We're going to build a dating app for cookies. 12 | 13 | 14 | 15 | 16 | 17 | Before starting make sure you have: 18 | 19 | * Android Studio installed: https://developer.android.com/studio 20 | * An Android simulator created: https://developer.android.com/studio/run/managing-avds.html 21 | * Alternatively you can use your own device: https://developer.android.com/studio/run/device.html 22 | * This repository cloned 23 | * The branch `01-start` checked out 24 | 25 | 26 | --- 27 | ## Organisation 28 | This workshop is organised in branches: 29 | 30 | ``` 31 | 01-start 32 | 02-cookie-repository 33 | 03-advanced-cookie-repository 34 | 04-ui-with-dynamic-data 35 | 05-advanced-ui-with-dynamic-data 36 | ``` 37 | 38 | Starting with `00-start` you'll be given a set of tasks to build up an app. Each branch is built on top of the previous one and contains a solution for the given tasks. So if you get stuck, just checkout the next branch and continue! 39 | 40 | ## 01-start 41 | 42 | This branch contains the basic structure for an Android app: 43 | 44 | * `MainActivity`: shows a layout with only dummy data. 45 | * `activity_main.xml`: a basic layout has already been created for you. 46 | * The rest of the project contents are the default files generated by Android Studio new project wizard. 47 | 48 | 49 | ### Your tasks 50 | 51 | * Go the `MainActivity` and change the app's layout **programatically** to show a hardcoded text for both cookie name and cookie description `TextView`s and a hardcoded image for the cookie `ImageView` 52 | 53 | > You can set images in a `ImageView` as `Drawables` or setting the resource directly. For more information, see: https://developer.android.com/reference/android/widget/ImageView 54 | 55 | * Bind an action to both buttons by setting up their click listeners 56 | 57 | > A visual way to debug user clicks is using [Toast messages](https://developer.android.com/guide/topics/ui/notifiers/toasts) 58 | 59 | 60 | ## 02-cookie-repository 61 | You have now a UI that can be set programatically, but we don't have any data for it! 62 | 63 | We need a repository class to get the data from. In this step you're going to build `CookieRepository` using TDD. 64 | 65 | 66 | 67 | ### Your tasks 68 | * There are a set of tests in `CookieRepositoryTest` that don't pass (they don't even compile at the moment!) 69 | * Add content to `CookieRepository` so all the test pass, 70 | * Add any other class you think is necessary, 71 | 72 | 73 | > In Kotlin, classes that are used only to hold data are called a [*data class*](https://kotlinlang.org/docs/reference/data-classes.html) and they have some cool features ;) 74 | 75 | ## 03-advanced-cookie-repository 76 | 77 | We have new requirements from our Project Manager: 78 | 79 | - The app must only show Cookies with pictures. We may receive Cookies without picture in our repository but the app must filter them out. 80 | - The list presented to the user needs to be sorted by Cookie name. 81 | - All Cookie names must be displayed in Camel Case format. 82 | 83 | 84 | ### Your tasks 85 | * There's a new test to model the new requirements and it's not passing. 86 | * Make the necessary changes in `fun getAllCookies(): List` so the new test pass 87 | 88 | > The Kotlin Standard Library contains a huge list of [tools for Collections](https://kotlinlang.org/docs/reference/collections-overview.html). 89 | 90 | ## 04-ui-with-dynamic-data 91 | We have a working repository class, it's time to use it! 92 | Go back to `MainActivity` and use the data from the repository to populate the UI. 93 | 94 | For this workshop, we're passing a hardcoded list of Cookies to the repository to simulate API fetched data. This has already been prepared for you. 95 | 96 | ### Your tasks 97 | * There's a `CookieRepository` instance created for you in `MainActivity` with some data. 98 | * Make changes in `MainActivity` so the displayed data is read from the repository. 99 | * Change like and dislike click listeners to: 100 | * Update like or dislike collections in the repository 101 | * Display the next available Cookie 102 | 103 | ## 05-advanced-ui-with-dynamic-data 104 | Final step! We have a working interface that reads data from a repository. Let's try to make it better. Here are some ideas: 105 | 106 | ### Your tasks 107 | * When the user reaches the end of the list show an empty state with a message (instead of a crash!) You can use the placeholder image from the beginning. 108 | * Add a new button to skip a Cookie without liking or disliking it. When the user reaches the end of the list, it must go back and present again only the skipped ones. 109 | * Add a new Activity to list all your liked Cookies. 110 | 111 | > In Android, list of UI elements are created using something called a [RecyclerView](See https://developer.android.com/guide/topics/ui/layout/recyclerview) 112 | 113 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | apply plugin: 'kotlin-android' 4 | 5 | apply plugin: 'kotlin-android-extensions' 6 | 7 | android { 8 | compileSdkVersion 28 9 | defaultConfig { 10 | applicationId "com.kotlinsevilla.cookietinder" 11 | minSdkVersion 23 12 | targetSdkVersion 28 13 | versionCode 1 14 | versionName "1.0" 15 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 16 | } 17 | buildTypes { 18 | release { 19 | minifyEnabled false 20 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | } 24 | 25 | dependencies { 26 | implementation fileTree(dir: 'libs', include: ['*.jar']) 27 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 28 | implementation 'androidx.appcompat:appcompat:1.0.2' 29 | implementation 'androidx.core:core-ktx:1.0.2' 30 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3' 31 | implementation 'androidx.recyclerview:recyclerview:1.0.0' 32 | implementation 'androidx.cardview:cardview:1.0.0' 33 | 34 | testImplementation 'junit:junit:4.12' 35 | testImplementation 'com.nhaarman.mockitokotlin2:mockito-kotlin:2.1.0' 36 | 37 | androidTestImplementation 'androidx.test:runner:1.2.0' 38 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' 39 | 40 | } 41 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/kotlinsevilla/cookietinder/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.kotlinsevilla.cookietinder 2 | 3 | import android.os.Bundle 4 | import androidx.appcompat.app.AppCompatActivity 5 | 6 | class MainActivity : AppCompatActivity() { 7 | 8 | override fun onCreate(savedInstanceState: Bundle?) { 9 | super.onCreate(savedInstanceState) 10 | setContentView(R.layout.activity_main) 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/cookie_oreo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lgvalle/androidkotlinworkshop/5d9ffc0501c7e48de8cbe1a62a4de42f0b788535/app/src/main/res/drawable-xhdpi/cookie_oreo.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/cookie_placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lgvalle/androidkotlinworkshop/5d9ffc0501c7e48de8cbe1a62a4de42f0b788535/app/src/main/res/drawable/cookie_placeholder.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 10 | 12 | 14 | 16 | 18 | 20 | 22 | 24 | 26 | 28 | 30 | 32 | 34 | 36 | 38 | 40 | 42 | 44 | 46 | 48 | 50 | 52 | 54 | 56 | 58 | 60 | 62 | 64 | 66 | 68 | 70 | 72 | 74 | 75 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 20 | 21 | 38 | 39 | 51 | 52 |