AWS 入门

构建 Android 应用程序

使用 AWS Amplify 创建简易 Android 应用程序

模块 1:创建和部署 Android 应用程序

在本模块中,您将创建 Android 应用程序并使用 AWS Amplify 的 Web 托管服务将其部署到云中。

简介

AWS Amplify 提供一个基于 Git 的工作流程,可用于为 Web 和移动应用程序创建、管理、集成和部署无服务器后端。Amplify CLI 提供一个简单的基于文本的用户界面,用于配置和管理应用程序的后端服务,例如用户身份验证或 REST 或 GraphQL API。您只需在应用程序中编写几行代码,就能从 Amplify 库轻松集成后端服务。

在本模块中,我们先创建一个用于记录旅行笔记的 Android 应用程序。每一个笔记由标题、描述和图片组成。我们将通过以下模块增强此应用程序的性能。

您将学到的内容

  • 创建 Android 应用程序
  • 更新显示各数据项列表的主视图
  • 构建和测试应用程序

重要概念

Kotlin:之所以选择 Kotlin 作为此示例应用程序的编程语言,是因为使用 Kotlin 可以减少许多样板代码,而且 Kotlin 的设计引入了类型安全机制。

Jetpack:本教程示例中使用 Android Jetpack。Android Jetpack 是一个 Android 库的集合,其中包含最佳实践场景并为 Android 应用程序提供向后兼容能力。

 时长

10 分钟

 使用的服务

操作步骤

  • 启动 Android Studio 并从初始屏幕中选择 Start a new Android Studio project(启动一个新的 Android Studio 项目)。

    在 Phone and Tablet(手机和平板电脑)下方,选择 Basic Activity(基础活动),然后点击 Next(下一步)。

    为您的项目键入名称,例如 Android Getting Started。确保语言为 Kotlin,开发工具包最低为 API 26: Android 8.0 (oreo),然后单击 Finish

    现在,项目的框架已经存在,我们需要完成 4 个步骤来让我们的基本应用程序运行:

    1. 删除不需要的类和文件,并添加插件
    2. 创建类以保留数据结构
    3. 创建视图以在列表中保留单个备注
    4. 修改 MainActivity 以显示备注列表
  • 在“res/layout”和 java/com.example.androidgettingstarted 下,删除四个片段文件(选择这 4 个文件,右键单击,然后从上下文菜单中选择 Delete ):

    Gradle Scripts下,打开 build.gradle (Module:app),并添加 Kotlin 扩展插件。

    plugins {
        id 'com.android.application'
        id 'kotlin-android'
        id 'kotlin-android-extensions' //  <== add this line
    }
  • UserData 类可保留用户状态:一个 isSignedIn 标记和一个备注值列表。

    要创建新类,请右键单击 java/com.example/androidgettingstarted,然后选择 New -> Kotlin file/class。键入 UserData 作为名称。

    将以下代码粘贴到新创建的文件 (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
    
        }
    }

    我们刚刚添加了哪些内容?

    • UserData 类负责保留用户数据,即一个 isSignedIn 标记用于跟踪当前的身份验证状态和备注对象列表。
    • 这两个属性是根据 LiveData 发布/订阅框架来实现的。它允许图形用户界面 (GUI) 订阅更改并做出相应反应。要了解关于 LiveData 的更多信息,您可以阅读此文档或学习本简短视频教程。要遵循最佳做法,请将 MutableLiveData 属性保留为私有,只公开只读 LiveData 属性。当要发布的数据是一个列表时,需要额外样板文件代码,以确保在修改列表中的单个组件时,系统会通知观察者。
    • 我们还添加了一个备注数据类,仅用于保留单个备注的数据。针对 ImageName 和 Image 使用了两个不同的属性。我们将在后续模块中介绍 Image。
    • 为 UserData 对象实施了单态设计模式,因为此模式允许只使用 UserData 即可从应用程序的任何位置引用此对象。
  • 滚动列表中的单个单元格称为 RecyclerView,因为当用户上下滚动屏幕,屏幕上再也看不到视图时,可以回收视图。

    与常规视图一样,我们也创建了布局 XML 文件和 Kotlin 类。单个单元格类似以下内容:

    要创建布局文件,请右键单击“res/layout”,然后选择 New -> Layout Resource File。键入 content_note 作为名称,并保留所有其他值,因为我们将直接编辑 XML 文件。

    打开新创建的文件的 Code 视图。

    在新创建的文件 (content_note.xml) 的“Code”视图中,通过粘贴以下代码来替代所生成的代码:

    <?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>

    最后,创建 Kotlin 类:右键单击 java/com.example/androidgettingstarted,然后选择 New -> Kotlin file/class。键入 NoteRecyclerViewAdapter 作为名称。

    将以下代码粘贴到新创建的文件 (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)
        }
    }

    我们刚刚添加了哪些内容?
    上述代码包含

    • 一个布局 xml 文件,该文件说明表示备注的单元格上的组件布局。它显示图像、备注标题和备注说明。
    • 一个支持 Kotlin 类。它在创建时接收备注数据对象,并将单个值分配给其相对应的视图(图像、标题和说明)
  • 现在,我们已经有了数据类(UserData 和备注)和单个备注的视图 (NoteRecyclerViewAdapter),让我们在主要活动上创建备注列表。

    从 Android Studio 左侧的文件列表,打开 res/layout/content_main.xml,并将代码替换为以下内容:

    <?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>

    从 Android Studio 左侧的文件列表,打开 java/com.example/androidgettingstarted/MainActivity.kt,并将代码替换为以下内容:

    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"
        }
    }

    我们刚刚添加了哪些内容?

    • 主要布局是一个 RecyclerView,用于管理我们之前创建的单个单元格列表
    • 主要活动类可观察备注列表的变化,并创建一个 NoteRecyclerViewAdapter 以创建单个单元格。
  • Gradle Scripts下,打开 build.gradle (Module:app),并验证所生成的依赖关系是否正确。需要选中 `libraries versions`

    ```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'
    }
  • 现在,请在模拟器中构建并启动应用程序。单击工具栏中的运行图标 ▶️ 或按 ^ R

    片刻之后,应用程序会在 Android 模拟器中启动,初始屏幕为空。

    在此阶段,没有要在运行时呈现的数据。我们将在稍后的步骤中删除邮件图标。

结论

您已成功创建了一个 Android 应用程序。下一步,使用 Amplify 构建这个 Android 应用程序!

初始化 Amplify