Getting Started with AWS

Build an Android Application

Create a simple Android application using AWS Amplify

Module 1: Create and Deploy an Android App

In this module you will create an Android application and deploy it to the cloud using AWS Amplify’s web hosting service.

Introduction

AWS Amplify provides a Git-based workflow for creating, managing, integrating, and deploying serverless backends for web and mobile applications. The Amplify CLI provides a simple text-based user interface to provision and manage backend services, such as user authentication or a REST or GraphQL API for your applications. The Amplify Libraries allow to easily integrate these backend services with just a few lines of code in your applications.

In this module, we’ll begin by creating a new Android application to take travel notes. A note is made of a title, a description and a picture. We will enhance this application in the following modules.

What You Will Learn

  • Create an Android application
  • Update the main view to display a list of items
  • Build and test your application

Key Concepts

Kotlin - Kotlin was chosen as the programming language for this tutorial because it allows you to reduce much of the boilerplate code and it is designed with type safety in mind.

Jetpack - This tutorial uses Android's Jetpack, a collection of Android libraries that incorporate best practices and provide backwards compatibility in your Android apps.

 Time to Complete

10 minutes

 Services Used

Implementation

  • Start Android Studio and select Start a new Android Studio project from the splash screen:

    Under Phone and Tablet, select Basic Activity and click Next:

    Type a name for your project, for example Android Getting Started. Make sure Language is Kotlin and Minimum SDK is API 26: Android 8.0 (oreo), then click Finish:

    Now that the skeleton of the project exists, we need 4 steps to get our basic app running:

    1. Delete unwanted classes and files and add plugins
    2. Create classes to hold our data structures
    3. Create a View to hold individual Note in the list
    4. Modify the MainActivity to display a list of Note
  • Under res/layout and java/com.example.androidgettingstarted delete the four fragment files (select the 4 files, right click and choose Delete from the contextual menu):

    Under Gradle Scripts, open build.gradle (Module:app) and add the Kotlin Extension plugin.

    plugins {
        id 'com.android.application'
        id 'kotlin-android'
        id 'kotlin-android-extensions' //  <== add this line
    }
  • The UserData class holds the user state : a isSignedIn flag and a list of Note values.

    To create a new class, right click java/com.example/androidgettingstarted and select New -> Kotlin file/class. Type UserData as name.

    Paste the code below in the new file just created (UserData.kt)

    package com.example.androidgettingstarted
    
    import android.graphics.Bitmap
    import android.util.Log
    import androidx.lifecycle.LiveData
    import androidx.lifecycle.MutableLiveData
    
    // a singleton to hold user data (this is a ViewModel pattern, without inheriting from ViewModel)
    object UserData {
    
        private const val TAG = "UserData"
    
        //
        // observable properties
        //
    
        // signed in status
        private val _isSignedIn = MutableLiveData<Boolean>(false)
        var isSignedIn: LiveData<Boolean> = _isSignedIn
    
        fun setSignedIn(newValue : Boolean) {
            // use postvalue() to make the assignation on the main (UI) thread
            _isSignedIn.postValue(newValue)
        }
    
        // the notes
        private val _notes = MutableLiveData<MutableList<Note>>(mutableListOf())
    
        // please check https://stackoverflow.com/questions/47941537/notify-observer-when-item-is-added-to-list-of-livedata
        private fun <T> MutableLiveData<T>.notifyObserver() {
            this.postValue(this.value)
        }
        fun notifyObserver() {
            this._notes.notifyObserver()
        }
    
        fun notes() : LiveData<MutableList<Note>>  = _notes
        fun addNote(n : Note) {
            val notes = _notes.value
            if (notes != null) {
                notes.add(n)
                _notes.notifyObserver()
            } else {
                Log.e(TAG, "addNote : note collection is null !!")
            }
        }
        fun deleteNote(at: Int) : Note?  {
            val note = _notes.value?.removeAt(at)
            _notes.notifyObserver()
            return note
        }
    
        fun resetNotes() {
            this._notes.value?.clear()  //used when signing out
            _notes.notifyObserver()
        }
    
    
        // a note data class
        data class Note(val id: String, val name: String, val description: String, var imageName: String? = null) {
            override fun toString(): String = name
    
            // bitmap image
            var image : Bitmap? = null
    
        }
    }

    What did we just add?

    • The UserData class is responsible to hold user data, namely a isSignedIn flag to track current authentication status and a list of Note objects.
    • These two properties are implemented according to the LiveData publish / subscribe framework. It allows the Graphical User Interface (GUI) to subscribe to changes and to react accordingly. To learn more about LiveData, you can read this doc or follow this short video tutorial. To follow best practice, keep the MutableLiveData property private and only expose the readonly LiveData property. Some additional boilerplate code is required when the data to publish is a list to make sure observers are notified when individual components in the list are modified.
    • We also added a Note data class, just to hold the data of individual notes. Two distinct properties are used for ImageName and Image. Image will be taken care of in a subsequent module.
    • Implemented the singleton design pattern for the UserData object as it allows referal to it from anywhere in the application just with UserData.
  • Individual cells in a scrolling list are called RecyclerView, as the View can be recycled when the user scrolls up and down, when the view is not visible on the screen anymore.

    Just like for a regular View, we create layout XML files and a Kotlin class. An individual cell looks like this:

    To create the layout file, right click res/layout and select New -> Layout Resource File. Type content_note as name and leave all the other values, as we will directly edit the XML file.

    Open the Code view for the newly created file.

    In the Code view of the new file just created (content_note.xml), replace the code generated by pasting the code below:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:paddingVertical="16dp">
    
        <ImageView
            android:id="@+id/image"
            android:layout_width="100dp"
            android:layout_height="match_parent"
            android:padding="8dp"
            android:scaleType="centerCrop"
            android:src="@drawable/ic_launcher_background" />
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:layout_marginLeft="5dp"
            android:orientation="vertical">
    
            <TextView
                android:id="@+id/name"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="Title"
                android:textSize="20sp"
                android:textStyle="bold" />
    
            <TextView
                android:id="@+id/description"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="Title"
                android:textSize="15sp" />
    
        </LinearLayout>
    </LinearLayout>

    Finally, create the Kotlin class: right click java/com.example/androidgettingstarted and select New -> Kotlin file/class. Type NoteRecyclerViewAdapter as name.

    Paste the code below in the new file just created (NoteRecyclerViewAdapter.kt)

    package com.example.androidgettingstarted
    
    import android.view.LayoutInflater
    import android.view.View
    import android.view.ViewGroup
    import android.widget.ImageView
    import android.widget.TextView
    import androidx.recyclerview.widget.RecyclerView
    
    // this is a single cell (row) in the list of Notes
    class NoteRecyclerViewAdapter(
        private val values: MutableList<UserData.Note>?) :
        RecyclerView.Adapter<NoteRecyclerViewAdapter.ViewHolder>() {
    
        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
            val view = LayoutInflater.from(parent.context)
                .inflate(R.layout.content_note, parent, false)
            return ViewHolder(view)
        }
    
        override fun onBindViewHolder(holder: ViewHolder, position: Int) {
    
            val item = values?.get(position)
            holder.nameView.text = item?.name
            holder.descriptionView.text = item?.description
    
            if (item?.image != null) {
                holder.imageView.setImageBitmap(item.image)
            }
        }
    
        override fun getItemCount() = values?.size ?: 0
    
        inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
            val imageView: ImageView = view.findViewById(R.id.image)
            val nameView: TextView = view.findViewById(R.id.name)
            val descriptionView: TextView = view.findViewById(R.id.description)
        }
    }

    What did we just add?
    The above code is made of

    • a layout xml file that describe the layout of component on a cell representing a Note. It displays the image, the note title, and the note description.
    • a support Kotlin class. It receives a Note data object at creation time and assign individual values to their corresponding views (image, title and description)
  • Now that we have the data classes (UserData and Note) and the individual note's view (NoteRecyclerViewAdapter), let's create the note list on the main activity.

    From the file list on the left of Android Studio, open res/layout/content_main.xml and replace the code with this content:

    <?xml version="1.0" encoding="utf-8"?>
    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/frameLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        >
    
        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/item_list"
            android:name="com.example.myapplication.ItemListFragment"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_marginTop="60dp"
    
            android:paddingHorizontal="8dp"
            android:paddingVertical="8dp"
            app:layoutManager="LinearLayoutManager"
            tools:context=".MainActivity"
            tools:listitem="@layout/content_note" />
    
    </FrameLayout>

    From the file list on the left of Android Studio, open java/com.example/androidgettingstarted/MainActivity.kt and replace the code with this content:

    package com.example.androidgettingstarted
    
    import android.os.Bundle
    import android.util.Log
    import androidx.appcompat.app.AppCompatActivity
    import androidx.lifecycle.Observer
    import androidx.recyclerview.widget.ItemTouchHelper
    import androidx.recyclerview.widget.RecyclerView
    import kotlinx.android.synthetic.main.activity_main.*
    import kotlinx.android.synthetic.main.content_main.*
    
    class MainActivity : AppCompatActivity() {
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
            setSupportActionBar(toolbar)
    
            // prepare our List view and RecyclerView (cells)
            setupRecyclerView(item_list)
        }
    
        // recycler view is the list of cells
        private fun setupRecyclerView(recyclerView: RecyclerView) {
    
            // update individual cell when the Note data are modified
            UserData.notes().observe(this, Observer<MutableList<UserData.Note>> { notes ->
                Log.d(TAG, "Note observer received ${notes.size} notes")
    
                // let's create a RecyclerViewAdapter that manages the individual cells
                recyclerView.adapter = NoteRecyclerViewAdapter(notes)
            })
        }
    
        companion object {
            private const val TAG = "MainActivity"
        }
    }

    What did we just add?

    • the main layout is a RecyclerView that manages the list of individual cells we created previously
    • the main activity class observes changes on the list of Notes and creates an NoteRecyclerViewAdapter to create individual cells.
  • Under Gradle Scripts, open build.gradle (Module:app) and verify that the generated dependencies are correct. The `libraries versions` need to be checked.

    ```gradle
    dependencies {
        implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
        implementation 'androidx.core:core-ktx:1.3.2'
        implementation 'androidx.appcompat:appcompat:1.2.0'
        implementation 'com.google.android.material:material:1.2.1'
        implementation 'androidx.constraintlayout:constraintlayout:2.0.2'
        implementation 'androidx.navigation:navigation-fragment-ktx:2.3.1'
        implementation 'androidx.navigation:navigation-ui-ktx:2.3.1'
        testImplementation 'junit:junit:4.+'
        androidTestImplementation 'androidx.test.ext:junit:1.1.2'
        androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
    }
  • Now build and launch the app in the simulator. Click the Run icon ▶️ in the toolbar or type ^ R.

    After a while, the app starts in the Android Simulator, with an initial empty screen.

    At this stage, there is no data to render at runtime. We will get rid of the mail icon in a later step.

Conclusion

You have successfully created an Android app. You are ready to start building with Amplify!

Was this page helpful?

Initialize Amplify