# Welcome

SP26 Edition!

There aren't many platforms that provide access to 3.3 billion active devices. With 71.85% of global market share (as of 2026) and growing, there's no question as to which platform dominates the world's smartphone market. It is the reigning champion in India and China, where the total population is approaching 3 billion combined, and internet access among the populace is growing by the second. If you want access to emerging markets, low barriers to entry, and the open-source community, you've got to go green.

Here at Cornell AppDev, we recognize that our user base comes from a broad range of backgrounds and uses a broad range of devices from Samsung, OnePlus, and Google. Since we've created our Android subteam in Spring 2018, we've started making strides toward cross-compatibility on all fronts. With Android applications such as Eatery, Uplift, Resell, and Ithaca Transit gaining ground on the Cornell campus, we can fairly say that we know what we're doing, and we'd like to pass it on to you.

In this course, you'll learn the basics of Android development. From a low-level understanding of what the system is doing to the usage of higher-level features such as networking and notifications, we'll attempt to impart our knowledge of industry-standard development of Android applications, along with any tips and tricks we find helpful along the way.


# Syllabus

Course Logistics!

## Welcome to Intro to Android Development!&#x20;

Hello and welcome to the class! We are your instructors for this course, Connie and Melissa, and we’re incredibly excited to help you learn how to make Android apps this semester. It wasn't too long ago that we were learning the intricacies of Android ourselves, and now we wish to pass on the knowledge we've learned to you. It’s going to be a great semester and we can’t wait to get started!

## Course Staff

*(If you have any questions or concerns, feel free to reach out the instructors at <mv477@cornell.edu> and <cl2736@cornell.edu>.)*

| Melissa Velasquez | Course Instructor |
| ----------------- | ----------------- |
| Connie Liu        | Course Instructor |
| Gregor Guerrier   | TA                |
| Andrew Cheung     | TA                |
| Amy Wang          | TA                |
| Emil Jiang        | TA                |
| Caleb Shim        | TA                |
| Ryan Cheung       | TA                |
| Brain Hu          | TA                |
| Danny McCance     | TA                |
| Mabel Qiu         | TA                |
| Abigail Labankok  | TA                |
| Preston Williams  | TA                |
| Jarmin Weng       | TA                |

If you have a question for the course staff, ask a private question on Ed Discussion or schedule a meeting during office hours.

## Course Schedule

{% hint style="info" %}
This schedule is tentative and subject to changes to Cornell's Spring 2026 academic calendar.
{% endhint %}

Lectures will be held in **Gates 114** at **8:35-9:25pm** every Monday and Wednesday.

All lectures will be recorded and posted to AppDev's [YouTube channel](https://www.youtube.com/channel/UCUDPjOx1yPRGxsO6AFtBZSg) after the lecture.&#x20;

Towards the end of the course, you'll be participating in the 2-week Hack Challenge alongside students from other AppDev courses, to create a full-fledged mobile application.

Outlined below is a schedule of our lectures, discussions, and projects (with their due dates).

<table><thead><tr><th>Class Dates</th><th width="158">Topic</th><th>Project Assigned</th><th>Project Due Date</th></tr></thead><tbody><tr><td>3/11</td><td>Course Logistics, Kotlin, &#x26; Basic UI (Lecture + Demo)</td><td>A1 (Ungraded)</td><td>A1 is Ungraded. </td></tr><tr><td>3/16</td><td>States, Components, &#x26; LazyColumn (Lecture)</td><td>A2</td><td>3/22 @ 11:59pm</td></tr><tr><td>3/18</td><td>(Demo)</td><td></td><td></td></tr><tr><td>3/23</td><td>Navigation &#x26; Animations (Lecture)</td><td>A3</td><td>4/6 @ 11:59pm</td></tr><tr><td>3/25</td><td>(Demo)</td><td></td><td></td></tr><tr><td>Spring Break!</td><td></td><td></td><td></td></tr><tr><td>4/6</td><td>MVVM &#x26; Flows</td><td>A4</td><td>4/12 @ 11:59pm</td></tr><tr><td>4/8</td><td>(Demo)</td><td></td><td></td></tr><tr><td>4/13</td><td>Coroutines, Networking, JSON</td><td>A5</td><td>4/19 @ 11:59pm</td></tr><tr><td>4/15</td><td>(Demo)</td><td></td><td></td></tr><tr><td>4/20</td><td>Dumb Components (MVVM Component Design) </td><td>A6</td><td>4/26 @ 11:59pm </td></tr><tr><td>4/22</td><td><del>(Demo)</del> Guest Speaker Presentation</td><td></td><td></td></tr><tr><td>4/27</td><td>Bonus Lecture: Getting started on the Hack Challenge</td><td></td><td></td></tr><tr><td>4/29</td><td>Bonus Lecture:<br>Developing with ML Kit</td><td></td><td></td></tr></tbody></table>

## Grading

**Note: for SP26, you can take this course for&#x20;*****up to*****&#x20;2 credits, in the event of credit overload. However, regardless of the number of credits you're signed up for, you are still expected to complete 2 credits worth of work.**

This is a **2 credit S/U course** and grading will be determined by all projects, with more emphasis on the Hack Challenge final project.&#x20;

Each project is worth 10-12 points, and the final project is worth 20 points: 10 points for the midpoint submission and 10 points for the final submission. Each project (with the exception of the first) will have extra credit opportunities for you to challenge yourself and dive deeper into Android development.&#x20;

**To pass this course, you will need >= 70** **points by the end of the semester.**

| Assignments       | Weight       |
| ----------------- | ------------ |
| A1                | 0 (Ungraded) |
| A2                | 13           |
| A3                | 13           |
| A4                | 13           |
| A5                | 13           |
| A6                | 13           |
| Hack Challenge    | 20           |
| Participation     | 15           |
| **Total**         | **100**      |
| **Passing Score** | **70**       |

### Attendance

Attendance is required for all students of the course for the lectures, and are permitted at most 3 absences (not including bonus lectures which do not require attendance). If you exceed this amount you will be given a 0 for participation. Attendance will be taken at the end of each lecture (and demo).

### Late policy

You are given 6 slip days that you can use on any of the assignments aside from the Hack Challenge project. Each assignment will be opened for late submissions for an extra 2 days after the regular deadline. If you submit an assignment late and run out of slip days, assignments will be automatically docked 2 points. &#x20;

### Note on grading for assignments:

If you are unable to finish the project as a result of an error, please add a note in a README.txt file explaining:

1. the error that you are encountering
2. the suspected cause of the error (doesn't have to be correct!)
3. the solutions you have attempted to resolve the error

This helps out the grader quickly find and debug errors and give partial points for effort!

It is also helpful to leave broken code in your project (but have it commented out). This will allow us to better understand your thought process and give you partial points for missing features.&#x20;

## Course Help

* If you ever need help, you can always post on the course [Ed Discussion](https://edstem.org/us/join/mUrfgB). We will be monitoring the class feed to be helping you! For more complicated questions, try to attend office hours if at all possible.

## Office Hours

* Office hours are held at multiple times throughout the week by instructors and TAs!&#x20;
* See the [OH Google Calendar](https://calendar.google.com/calendar/u/0?cid=Y183ODNhOTQ5YjJiM2IwODUyOWNmMWNjNDhiNTNjMjQ0N2NhYjJmNzQ2MjBjZTgwMTk3ZGNlNDVhZTYzMWYyODE5QGdyb3VwLmNhbGVuZGFyLmdvb2dsZS5jb20) for office hour times!
* Check Ed Discussion for any TA announcements for OH adjustments.

(OH starts on the week of 3/16. If you need any course help before that, feel free to stay after class and ask the instructors questions, or ask on Ed Discussion!)

We are here to help you succeed! Our number one goal is for you to LEARN Android development and we will be with you each step of the way to make sure that happens 👏.

## Hack Challenge

At the end of the course, you and up to three other members of AppDev courses (Intro to Digital Product Design, Intro to Backend Engineering, Intro to iOS) will have the chance to work in a group and apply all that you’ve learned in the course to create a functional, polished app.&#x20;

Participation in the Hack Challenge is **required** as the final project for this course. The course staff will judge the best apps and there will be sponsored prizes! Last semester’s winners won Google Homes, bluetooth speakers, Echo Dots, or Chromecasts. This could be you this semester, so start brainstorming ideas!

## Academic Integrity & Collaboration

As with any other course at Cornell, the code of academic integrity will be enforced in this class. All individual projects must be done by yourself. All University-standard Academic Integrity guidelines should be followed. This includes proper attribution of any resources found online, including anything that may be open-sourced by AppDev. The University guidelines for Academic Integrity can be found [here](https://theuniversityfaculty.cornell.edu/academic-integrity/).

We encourage using the internet and AI to learn more about Android development, but again, any code you submit must be written by you!


# Git & GitHub Help/How-To

## Git Download: <https://git-scm.com/downloads>

With Android Studio, you don't need to use the terminal to contribute to an Android project on GitHub. It has native integration with git and GitHub to allow most actions via the Android Studio UI. Below gives an overview of some of these actions!

## Cloning the repository <a href="#cloning-the-repository" id="cloning-the-repository"></a>

If your partner has already created a repo and added files, to get on the same repo, you must clone it! (You do not have to create another repo yourself!) If the repo is private, you must be added as a collaborator first (instructions [here](https://docs.github.com/en/account-and-profile/setting-up-and-managing-your-github-user-account/managing-access-to-your-personal-repositories/inviting-collaborators-to-a-personal-repository)).

When you clone a repository, you copy the repository from GitHub.com to your local machine. Cloning a repository **pulls down a full copy of all the repository data that GitHub.com has at that point in time, including all versions of every file and folder for the project**.

When you open Android Studio, it offers the option to open a project from version control. That's the option we'll use (you can also create a new Android Studio project from version control)

![](https://195521982-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LvOeSx5ZqjJA8sxykMu%2Fuploads%2FgExRIu3kGDQF73CJZVJZ%2F8SEqyls.png?alt=media\&token=e36a389f-841d-43ff-a3f0-161c40460970)

![](https://195521982-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LvOeSx5ZqjJA8sxykMu%2Fuploads%2FqTRcnKq4j5oRJ8hAptT8%2Fimage.png?alt=media\&token=c36137dc-1076-48fc-9c4c-4041d74ac7b8)

After selecting that option, you can type the URL of the repository, press "Clone", and select a folder. After that, Android Studio will do all the work and open the project ready to go.

![Make sure you choose the correct account to clone from!](https://195521982-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LvOeSx5ZqjJA8sxykMu%2Fuploads%2FCNzvxWXyU4J3NKYx5gkd%2FbM92C6R.webp?alt=media\&token=b7eb64cd-388a-4688-a047-b2f1478714e0)

## How can we create repositories?

Creating repositories in Android Studio is pain-free (hopefully). If you're working with multiple people, only one person needs to create the repository!

First we have to add Git managing to our current Android Studio project

Go to: *VCS -> Create Git Repository*

![Your files will turn red, don't worry!](https://195521982-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LvOeSx5ZqjJA8sxykMu%2Fuploads%2F7mdJiEoJNR1Rb8u6tRAF%2Fimage.png?alt=media\&token=48c4a9b5-5ac9-4f9c-ae37-691b6a41043c)

Now, we have to create the repository on GitHub and share our files there!

Go to: *Git -> GitHub -> Share Project on GitHub*

![](https://195521982-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LvOeSx5ZqjJA8sxykMu%2Fuploads%2FkLZm5QdYtmXHzF6mc38j%2Fimage.png?alt=media\&token=d5052927-f3dd-4177-ab29-b0073e699191)

![](https://195521982-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LvOeSx5ZqjJA8sxykMu%2Fuploads%2FXNdS4tpxL72Orwz4FNUJ%2Fimage.png?alt=media\&token=195746e4-d5d1-4a10-8b0f-7688c03e1554)

You can then add your files to your newly created GitHub repository!

![](https://195521982-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LvOeSx5ZqjJA8sxykMu%2Fuploads%2FY4HPRoONC6tjtoBnLNnj%2Fimage.png?alt=media\&token=671bfb9c-1ddb-4b48-858c-9ab543affa11)

![](https://195521982-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LvOeSx5ZqjJA8sxykMu%2Fuploads%2F52jCT9HTNosLG7g8VWcu%2Fimage.png?alt=media\&token=bb1485b6-a223-4538-b334-d473e41056ee)

#### If your repository is private, you should add your collaborators to your repository so they have access too (which you can now do with your newly created repo). The instructions for that are [here](https://docs.github.com/en/account-and-profile/setting-up-and-managing-your-github-user-account/managing-access-to-your-personal-repositories/inviting-collaborators-to-a-personal-repository).  Only the creator of the repository can add collaborators!

#### Make your repositories PRIVATE unless stated otherwise (e.g hack challenge)

## Making the change <a href="#making-the-change" id="making-the-change"></a>

Initially, the default branch will be selected most likely master. Let's create a branch for our change based on the master branch! It's bad practice to make changes directly onto our master branch so we can utilize branches to create developmental spaces at the current state of the master without affecting the master branch!

![](https://195521982-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LvOeSx5ZqjJA8sxykMu%2Fuploads%2Fs4A88LJEmQgbBbUmEDV9%2Fimage.png?alt=media\&token=97f42ef5-be43-4c5a-a0d0-1bad64fa2a5a)

To do that, click where it says "Git: \[default branch]", select the master branch, click "New Branch from Selected", and choose a name for your branch.

![](https://195521982-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LvOeSx5ZqjJA8sxykMu%2Fuploads%2FHpjpdHyDHMxcnsJ6vJEo%2Fimage.png?alt=media\&token=7118dd68-9310-451b-b651-fa13b4bfb243)

Once you've actually made some changes to your new branch you have to commit them and push them online!

## Committing and pushing the change <a href="#committing-and-pushing-the-change" id="committing-and-pushing-the-change"></a>

Now, we can commit our change. To do this, either press CMD+K (or CTRL+K on Windows) to bring up the commit panel!

![Commit panel](https://195521982-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LvOeSx5ZqjJA8sxykMu%2Fuploads%2FbDMzMSA9B5hrjO4tpNWC%2Fimage.png?alt=media\&token=6ed199a4-6a90-407d-adf3-31dfca146073)

Review your changes in the screen provided, write a descriptive commit message, select commit and push on the bottom. That will take you to the screen where you'll choose which repository the branch will be uploaded. If you're a maintainer of the origin remote, you can press "Push" and skip to "Creating the pull request".

![](https://195521982-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LvOeSx5ZqjJA8sxykMu%2Fuploads%2FmJxKHzh4bfCAkw6DiX1J%2Fimage.png?alt=media\&token=65928be6-b10b-4166-9810-53eef2c14ac1)

## Creating the pull request <a href="#creating-the-pull-request" id="creating-the-pull-request"></a>

Now, go to your repository page on GitHub, and you'll see a prompt to open a Pull Request with your newly pushed branch.

When you press that, it will take you to a Pull Request composer.&#x20;

The **Pull Request** (PR for short) is the method by which — specifically using Git and GitHub — you can loop interested parties into reviewing and then approving your change, then merging it into some branch (presumably the trunk). This is where you explain the “whats” and “whys” of your code. Feel free to add your partners as reviewers for your code!

Once your PR has been reviewed and is "ready", it can then be merged into your master branch!

## Updating your local files

With new changes being pushed from your PR into your master branch (from either yourself or your partner), your local files in Android studio are out of date! You can update your files by "pulling" the changes from your repository online into your local repository!

In the top right of your project there's a small toolbar for Git. The blue check mark will update your project!

![The buttons to the right commit and push respectively!](https://195521982-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LvOeSx5ZqjJA8sxykMu%2Fuploads%2FEonzHstrgUaUMMkWxjVU%2Fimage.png?alt=media\&token=98e4e4ed-7e03-4ee3-b81d-831ae8375188)


# Setting up Android Studio

Setting up Android Studio takes just a few clicks.

First, be sure you [download the latest version of Android Studio](https://developer.android.com/studio/).

## Windows

To install Android Studio on Windows, proceed as follows:

1. If you downloaded an `.exe` file (recommended), double-click to launch it. If you downloaded a `.zip` file, unpack the ZIP, copy the **android-studio** folder into your **Program Files** folder, and then open the **android-studio > bin** folder and launch `studio64.exe` (for 64-bit machines) or `studio.exe` (for 32-bit machines).
2. Follow the setup wizard in Android Studio and install any SDK packages that it recommends.

That's it. As new tools and other APIs become available, Android Studio tells you with a pop-up, or you can check for updates by clicking **Help > Check for Update**.

## Mac

To install Android Studio on your Mac, proceed as follows:

1. Launch the Android Studio DMG file.
2. Drag and drop Android Studio into the Applications folder, then launch Android Studio.
3. Select whether you want to import previous Android Studio settings, then click **OK**.
4. The Android Studio Setup Wizard guides you through the rest of the setup, which includes downloading Android SDK components that are required for development.

That's it. As new tools and other APIs become available, Android Studio tells you with a pop-up, or you can check for updates by clicking **Android Studio > Check for Updates**.

## Linux

To install Android Studio on Linux, proceed as follows:

1. Unpack the `.zip` file you downloaded to an appropriate location for your applications, such as within `/usr/local/` for your user profile, or`/opt/` for shared users.
2. To launch Android Studio, open a terminal, navigate to the `android-studio/bin/` directory, and execute `studio.sh`.
3. Select whether you want to import previous Android Studio settings or not, then click **OK**. The Android Studio Setup Wizard guides you through the rest of the setup, which includes downloading Android SDK components that are required for development.

*Tip: To make Android Studio available in your list of applications, select **Tools > Create Desktop Entry** from the Android Studio menu bar.*

**Required libraries for 64-bit machines:**

If you are running a 64-bit version of Ubuntu, you need to install some 32-bit libraries with the following command:

```bash
sudo apt-get install libc6:i386 libncurses5:i386 libstdc++6:i386 lib32z1 libbz2-1.0:i386
```

If you are running 64-bit Fedora, the command is:

```bash
sudo yum install zlib.i686 ncurses-libs.i686 bzip2-libs.i686
```

That's it. As new tools and other APIs become available, Android Studio tells you with a pop-up, or you can check for updates by clicking **Help > Check for Update**.

## [More Android Studio Help](https://android-course.cornellappdev.com/chapters/1.-introduction-to-the-editor/1.5-demo-wip)


# Starting an Android Studio Project & Making an Emulator

See [1.5 Android Studio Project Demo + Understanding The Editor](/chapters/1.-introduction-to-the-editor/1.5-demo-wip)!


# Importing, Exporting, & Submitting Your Projects to CMS

## Importing .zip Files into Android Studios

To import a .zip file into Android Studios

* Select File (in the top left)
* New
* Import Project
* Navigate to the folder of the .zip file and select it

Alternatively,  if that doesn't work then we can try the following:

* Unzip the project into an EMPTY directory (using WinRAR or any built in unzipper)
* Open Android Studios
* Select File
* New
* Import Project
* Navigate to the root of said Directory
* Select the Android PROJECT not the zip file (see below)

<div align="left"><img src="https://195521982-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LvOeSx5ZqjJA8sxykMu%2F-MUg76zQta79dj4asE4a%2F-MUg8RewIBPwydCo6hXH%2Fimage.png?alt=media&amp;token=231de7c9-cf1f-4ee4-b3f7-530eeef63bc7" alt=""></div>

## Exporting Your Project as a .zip File

To export your project:

* Select File (top left of Android Studio)
* Find Export tab
* Export to Zip File
* Select the Directory you want the zip file to go in
* Name you file
* Select OK

## Submitting Your Project on CMS

Please name the .zip file with the format: **netid1\_HW#**. For example, if your netID is abc123 and you're working on HW2, you should name your project "abc123\_HW2".

## Challenge Problems <a href="#challenge-problems" id="challenge-problems"></a>

If you do any of the additional challenge problems, **please attach a plaintext file named "README" and list the challenge numbers that you implemented.**

For example, an assignment might have 4 challenge problems and if you implemented challenge problem #1 and #3, your README.txt will have "1, 3" on it. If you want to go into detail on what you implemented, this would be a good place to do so!

If you do not implement any of the challenge problems, you do not have to attach a README.


# Week 1 | Course Logistics, Kotlin, & Basic UI

Welcome to Intro to Android! :D

In week 1, we'll learn about our course logistics, some basics about Kotlin, and how to build basic UI with Jetpack Compose built-in components and modifiers!


# Relevant Links

**Google Slides:** [**\[Week 1 Slides\]**](https://docs.google.com/presentation/d/1lc_xFMMpPzrXNFaBcgGe_1Acu7gfajpnnLXlKmUpoeE/edit?slide=id.g385da76c1c9_0_1613#slide=id.g385da76c1c9_0_1613)

**Relevant Textbook Sections**: [\[Syllabus\]](/syllabus), [\[1.3 Kotlin Overview\]](/chapters/1.-introduction-to-the-editor/1.3-java-review), [\[2.2 Layouts\]](/chapters/2.-jetpack-compose/2.2-layouts), [\[2.3 Modifiers\]](/chapters/2.-jetpack-compose/2.3-modifiers)

**YouTube Recordings**: [\[Week 1 Lecture/Demo Link\]](https://youtu.be/7SJh1qtEqPA)

Lecture Demos + Solutions *(same link for all weeks)*: [\[GitHub Link\]](https://github.com/AndrewCheung360/intro-to-android-lecture-demos-fa25)

Demo/A1 (EateryCard) Solution: [\[GitHub Link\]](https://github.com/thisjustin123/intro-android-d1)

ExpenseRow Starter Code:

{% file src="/files/vzPrUEchhpHQiKg5Eylq" %}


# Demo/Lecture: Eatery Card

Using Modifier and a component's arguments to visually build a complex component!

In this demo, we'll be showing off how to make the following Eatery Card component, which is very similar to (albeit a bit simpler than) one you've seen if you regularly use Eatery:

<figure><img src="https://195521982-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LvOeSx5ZqjJA8sxykMu%2Fuploads%2Fg1GOQLc9WKnWA20HZHh3%2Fimage.png?alt=media&amp;token=536fe305-e7d7-46cd-bf14-efb42b9c6fc8" alt=""><figcaption></figcaption></figure>

If time permits, we'll then show how to use this component we've built on a screen to show things on an actual app!

### Step 1: Set up your Android Studio Project (follow-along)

First, we'll show you how to set up an Android Studio project!

1. Open Android Studio (you should have it already installed!)
2. Click **New Project** or go to **File -> New\... -> New Project...**
3. Make an **Empty Activity** Project.
4. Name your project something comprehensible like **D1**. Click Finish.

You should now have a new app created for you! In the future you can follow this procedure whenever you are asked to make a new project.

***

### Step 2: Create New Files (follow-along)

Next, we'll set up some files so that we can start building components and screens!

<figure><img src="https://195521982-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LvOeSx5ZqjJA8sxykMu%2Fuploads%2FXU4C6qJxeGghtJKNqFqR%2Fimage.png?alt=media&amp;token=f08b7ce9-b608-49c4-b979-7dc188d40cd5" alt=""><figcaption></figcaption></figure>

If you expand your filesystem, this is what you should see. The folder `com.[...].d1` is where you'll be adding Kotlin Android files.

1. Right Click `com.[...].d1`and do **New\... -> Kotlin Class/File.**&#x20;
2. Make a file named `EateryCard` ; this will become our card component.
3. Do the same and make a file named `MainScreen`.

Now, let's set up `EateryCard` so we can be ready to dev!

***

### Step 3: Set up EateryCard (follow-along)

Add the following functions to `EateryCard`:

<figure><img src="https://195521982-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LvOeSx5ZqjJA8sxykMu%2Fuploads%2F5e917oPQ6A3vs0QXyU7J%2Fimage.png?alt=media&amp;token=fd66ff83-0b7f-4147-9c03-c5c9929f9812" alt=""><figcaption></figcaption></figure>

These two functions represent the following:

1. EateryCard is the actual component we are building.
2. EateryCardPreview allows us to preview the component as we are building it so we can verify that what we are building looks right.

Next, click on this button on the top right:

<figure><img src="https://195521982-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LvOeSx5ZqjJA8sxykMu%2Fuploads%2FqVB0bHRNziZ9WfvNDrdL%2Fimage.png?alt=media&amp;token=1aa760bf-4ba1-4485-b056-ea8137234479" alt=""><figcaption></figcaption></figure>

This launches the preview so you can see what your component looks like as you're building it!

You may need to periodically `Build & Refresh`to see the component.

Because you haven't actually written anything in the component, nothing will show up:

<figure><img src="https://195521982-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LvOeSx5ZqjJA8sxykMu%2Fuploads%2FIHLHmGKaReJ0eMU0x5iJ%2Fimage.png?alt=media&amp;token=ddfa4994-3b16-4d7d-98cd-8f6327c0f7f1" alt=""><figcaption></figcaption></figure>

But now we're ready to dev! These next parts are on your own. We'll provide hints as we go. Feel free to ask us questions!

***

### Task 1: Build the outer Card

{% hint style="info" %}
Time to do things yourself now! Please feel free to ask questions as you go :-)
{% endhint %}

Use a Surface  component built into Android to make the card background. When asked to import `Surface`and any other Android components, always select the @Composable one.

*Hint 1: Surface always needs an inner body Composable, so for now just add a {} after the Surface to get it to compile. You'll add the rest of your stuff here.*

*Hint 2: Roll over Surface to see the arguments it can take in. You'll want to make this a RoundedCornerShape with a corner radius of 12.dp with a shadow elevation of 4.dp.*

*Hint 3: We want the card to fill the max width and have a fixed height of 220.dp. Use a Modifier for this!*

You should end up with the following:

<figure><img src="https://195521982-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LvOeSx5ZqjJA8sxykMu%2Fuploads%2FZyfb756malE2JkwqDcHn%2Fimage.png?alt=media&amp;token=8b1d55fe-8651-4d3f-93dc-733aeb9781b6" alt=""><figcaption></figcaption></figure>

***

### Task 2: Divide the Card Vertically

We want the card to have a distinct upper and lower section. Utilize a `Column` and `Modifier.weight(1f)` on the inner components in that column to make two `Box`es that take up the same vertical space!

Make the upper box a nice shade of blue.

*Hint 1: Boxes are 0 width and 0 height by default if they don't have any content. Make sure your box is filling the max width if it has a background to show its color!*

*Hint 2: Use Modifier.background(...) with the following color to achieve an Eatery Blue color:*

```
Color(0xff5ecfdb)
```

<figure><img src="https://195521982-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LvOeSx5ZqjJA8sxykMu%2Fuploads%2FqFe5xqsMIsQeCyzxlE5e%2Fimage.png?alt=media&amp;token=bab1cd76-1257-42ba-81d2-77825fa38116" alt=""><figcaption></figcaption></figure>

***

### Task 3: Add Texts

Next, let's make our eatery card display text!

We want our texts to display generally in the photo below. Don't worry that the Color is Red; up to you if you want to change that.

<figure><img src="https://195521982-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LvOeSx5ZqjJA8sxykMu%2Fuploads%2F6YeGESuoRUe7QppwCtGi%2Fimage.png?alt=media&amp;token=452107ef-c645-498d-b62d-f9ef3b374f28" alt=""><figcaption></figcaption></figure>

You'll need to use a combination of Rows, Columns, Texts, Modifiers, and a careful look at the arguments each of these take in to get this look!

*Hint 1: Modifier.padding() adds some padding around a container.*

*Hint 2: Row's horizontalArrangement parameter w/ SpaceBetween may be useful to push the Open text off to the right.*

*Hint 3: Remember to keep using fillMaxWidth() if you want your container to take up as much width as possible!*

*Hint 4: Take a look at the parameters of Text to see if you can roughly match our Text's intricacies.*

***

### Task 4: Run a screen & deploy the app!

Now that we've made our component, let's run it to make it show up on our emulator / app!

1. First, hop over to `MainScreen` . This file will represent our screen that the user will see.
2. Just like before, make a `MainScreen` @Composable function. Add just the Eatery Card to this screen.
3. Move over to MainActivity and replace the default `Scaffold` with our MainScreen.
4. Run the app!

<figure><img src="https://195521982-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LvOeSx5ZqjJA8sxykMu%2Fuploads%2Fv7gJLoQomkDZFGdFdFr9%2Fimage.png?alt=media&amp;token=fdf7c7e1-9918-40b3-aa5d-7b81dce384c2" alt=""><figcaption><p>How to run your app!</p></figcaption></figure>

(You should have an emulator set up for you automatically in android studio.)&#x20;

You should see your component. However, it hugs the sides kind of ugly...ly. Can you change `MainScreen` to make it fit more snugly?

If you see this, congrats. You just completed D1!

<figure><img src="https://195521982-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LvOeSx5ZqjJA8sxykMu%2Fuploads%2FgqKSvYVIfVU0kjSNyX5j%2Fimage.png?alt=media&amp;token=70afae67-f914-44b6-bd16-79adaad22983" alt=""><figcaption><p>End result...!</p></figcaption></figure>


# A1: Eatery Card (Follow-Along)

First-week free!

For this week, there is no need to submit an assignment on CMS, but feel free to practice submitting an assignment for the course. This is partially motivated by the fact that people often enroll in the class late, so we're not grading this assignment.

Please complete the Eatery Card Demo for this week! In that, we will be going over the workings of Android Studio and the coding of a custom Eatery Card component with Jetpack Compose.

We'll be showcasing some very important techniques about how to navigate Android Studio for this demo and build components.

**Demo/Lecture:**

{% content-ref url="/pages/fZQ7Wb0t5tyZmihUh4Go" %}
[Demo/Lecture: Eatery Card](/course-material/week-1-or-course-logistics-kotlin-and-basic-ui/demo-lecture-eatery-card)
{% endcontent-ref %}

Completing this lecture/demo is **crucial** for you to be able to complete future assignments, so please do not skip this! We trust you all!


# Week 2 | States, Components, LazyColumn

Apps need their UI to change to be at all useful...

In week 2, we'll learn how to leverage **states** to get our UI to show important data, build components to enforce good coding practice, and use LazyColumns to up our layout game!


# Relevant Links

**Google Slides:** [**\[Slides Link\]**](https://docs.google.com/presentation/d/1joB_mwAWjYT5jhIni71tHcN0i7cYQboIz53jXWyFOls/edit?usp=sharing)

**Relevant Textbook Sections**: [\[2.6 Reactive UI\]](/chapters/2.-jetpack-compose/2.6-reactive-ui), [\[2.5 Lazy Lists\]](/chapters/2.-jetpack-compose/2.5-lazy-lists)

**YouTube Recordings**: \[[Week 2 Lecture](https://youtu.be/P6hpwQ9ZlOg)], \[[Week 2 Demo](https://youtu.be/nFXStkG3m7s)]

**Week 2 Demo Solution**: [\[GitHub Link\]](https://github.com/melissavelasquezz/Intro-to-android-demo-solutions/tree/main/demo2soln/app/src/main/java/com/example/demo2_soln)


# Demo: Todo List

Some more practice with components and states!

In this demo, we'll build a simple todo list app with functionality to add new todos and display them in a scrolling list using components.

<figure><img src="https://195521982-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LvOeSx5ZqjJA8sxykMu%2Fuploads%2F82fvu7YJHYKMSRjP0amG%2Fdemo2.png?alt=media&amp;token=302e8527-c6ac-49fc-bef2-ced872e55bfe" alt="" width="188"><figcaption></figcaption></figure>

You'll start with an Empty Activity Project in Android Studio (see last week's demo for a refresher on how to do this)

Let's break this screen down into its individual components to help you get started:

***

### Step 1: Set Up Your TodoScreen.kt File&#x20;

Start by creating a file for your screen. Let's call this one \`TodoScreen.kt\`

We'll also be using a custom component within our screen called TodoCard, which we'll go ahead and define in this file along with the TodoScreen composable.

```kotlin
@Composable
fun TodoScreen(){
    //...
}

@Composable
fun TodoCard(){
    //...
}
```

The TodoScreen is made up of multiple components that are visually "stacked" on top of each other, so we want to wrap everything in a Column component

To achieve the correct alignment of the contents of the Column, make use of the the modifier, horizontalAlignment, and verticalArrangement arguments.

***

### Step 2: "My Todo List"

To start, make the title of the screen with font size 36.dp

Add a Spacer component with height 30.dp after this to make the screen more visually appealing

***

### Step 3: TextField

This is where you'll be able to enter a new todo to be added to the list below.

To start, TextFields are valuable since they allow users to input some data that can then be stored, manipulated, and/or displayed later. Since the TextField value can be manipulated, let's use a MutableState to represent it.&#x20;

```kotlin
var newTodo by remember{ mutableStateOf("") }
```

Like most components, TextField has a lot of different arguments possible. You'll usually only need to use four of them: modifier, placeholder, value, and onValueChange.&#x20;

* If you roll over TextField to look at all of the arguments, you'll notice that there's some arguments with a type that looks like some variant of  this () -> Unit. This means that you'll need to pass in a function.
  * Functions passed into arguments can either be defined elsewhere and called inline, or they can be defined inline within {}.
* This can be a little tricky, so give it a try yourself and check the answer when you need to!

<details>

<summary>TextField Arguments</summary>

```kotlin
TextField(
    modifier = Modifier.fillMaxWidth(),
    placeholder = { Text("new todo") },
    value = newTodo,
    onValueChange = { newText ->
        newTodo = newText
    }
)
```

Please ask if you have any questions about this!

</details>

Now when you build your code, you should be able to type in the TextField in the emulator!

***

### Step 4: Button

When the Add Todo button is pressed, we want the current value of the TextField to be stored somewhere. For that we'll use the following:

```kotlin
val todoList = remember{ mutableStateListOf<String>() }
```

<details>

<summary>Button Syntax</summary>

```kotlin
Button(
    modifier,
    onClick = { }//what happens when the button is clicked 
){
    //the visual contents of your button
    //typically a Text component with the label that you want to use for the button.
}
```

</details>

* In the onClick argument of your Button, add the current value of the TextField to the todoList.&#x20;
  * hint: if you want to see the possible functions on todoList, type todoList dot and look over the list that comes up.&#x20;
* We also want to clear the TextField after the current value is stored in the list. To do this, we can also set newTodo = "" in the onClick argument.
  * hint: functions within the argument's curly braces need to be separated using a semicolon.
* Label the Button using a Text composable within the Button component's curly braces.&#x20;

Now when you build the app, the TextField should reset to display the placeholder when you press the button.&#x20;

* Behind the scenes, the TextField value is being stored in todoList before it's cleared, but we aren't making any UI updates when that happens so we can't see it just yet!

Add another Spacer with height 30.dp here!

***

### Step 5: TodoCard

<figure><img src="https://195521982-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LvOeSx5ZqjJA8sxykMu%2Fuploads%2FIz5gsFS92i0IjLcTRBzE%2FScreenshot%202025-03-13%20at%208.44.23%E2%80%AFPM.png?alt=media&amp;token=3fe421ff-2fc4-4578-88ec-7cfc4b1f0c49" alt=""><figcaption></figcaption></figure>

Todos will be displayed as a LazyColumn of TodoCards. Let's build that now!

Each card is going to be a stylized Row component. We'll want to use the modifier, padding, and background arguments.

* Note that the order in which you call Modifiers affects the appearance of the card. Pay attention to the order of the .padding() and .background() modifiers and see if you can figure out what's happening there.

Additional Specs:

* Add padding so that when the TodoCards are eventually displayed in a LazyColumn there's some vertical space between each card.
* The example card is LightGray, one of the Color class's default colors
* The TodoCard has corners with "roundness" of 12.dp
* Use 20.dp of padding between the Text and the edges of the card.

Write an @Preview composable for TodoCard and check that it matches the sample above!

### Step 6: LazyColumn

Now that we have our TodoCard, let's use them to display our todoList with a LazyColumn.

Make a LazyColumn component in the TodoScreen that:

* Displays TodoCards with data from todoList
* Has height 200.dp

<details>

<summary>LazyColumn Syntax</summary>

```
LazyColumn(){
    items(some-list){
        some-composable(it) 
    }
}
//'it' is an implicitly declared name for a single-parameter lambda expression.
//it pretty much acts as each element of some-list so you don't need to iterate through some-list manually
```

You might need to do two imports on the items() code. Hover over any red lines to see if you missed an import!

</details>

hint: Use the verticalAlignment argument with Alignment.spacedBy() in the LazyColumn to make space between each TodoCard when they're displayed.

Run your app again. Now, when you type in a todo and hit the Add Todo button, you should see it appear below in order below the Button.&#x20;

When you add enough todos, you'll be able to scroll through the LazyColumn!

### Congrats, you've completed the demo! :)


# A2: Shopping List

Keeping track of expenses!

{% hint style="info" %}
As this is a smaller-scope assignment, there is no release code. Simply start from an empty Compose activity / new project.
{% endhint %}

For our second (and first graded) assignment, we'll be making a simple app to keep track of some expenses!

We'll need to leverage states and make some components to, in the end, use a LazyColumn to show our expenses in the following UI:

<figure><img src="https://195521982-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LvOeSx5ZqjJA8sxykMu%2Fuploads%2F0xkDyE7hew2UrYcxYbQO%2Fimage.png?alt=media&amp;token=a0d2f36e-1e0a-44b7-83b2-cef95bf6fa4c" alt=""><figcaption><p>(An @Preview of) the final product!</p></figcaption></figure>

## Requirements:

(expand each file below to see your requirements!)

<details>

<summary>ExpenseRow.kt (3pt)</summary>

We'll start off by making a very simple UI for an expense row in our app!

In general, try to match this UI:

<img src="https://195521982-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LvOeSx5ZqjJA8sxykMu%2Fuploads%2FzdM9qC9ez47i17z2zJWI%2Fimage.png?alt=media&amp;token=c6b47b3b-d090-40a7-a846-14879d01d846" alt="" data-size="original">

1. The expense row should have a title section for the expense title.
2. Below, the expense row should have a time section for the date/time the expense occurred.
3. To the far right, the expense row should have the expense.
   1. This text must be **bold.**
   2. If the amount is positive, it should be green. Otherwise, red.

Hints:

* Although not required, we *highly recommend* making a @Preview function in this file so you can see your expense row as you are developing!
* If you are unsure of how to specifically match this UI, look at Modifiers and the input arguments to components like `Text`  , `Row`, and `Column`.
* Use the following logical code to convert a `amount`number (Float) to a nice 2-digit-formatted string:

```kotlin
    // Format 2 digits after the decimal
    val formatted = "%.2f".format(abs(amount))
    val amountString = if (amount < 0) {
        "-$$formatted"
    } else {
        "+$$formatted"
    }
    // use amountString...
```

</details>

<details>

<summary>ExpenseHeader.kt (4pt)</summary>

In this file, you'll make the app's top header which relies on user input to allow the user to input new expenses into the app.

In general, try to match this UI:

![](https://195521982-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LvOeSx5ZqjJA8sxykMu%2Fuploads%2FLPsT9hxlUV3lCkQNgvM5%2Fimage.png?alt=media\&token=521ff28d-7bbb-423e-be07-1f52a941e42a)

1. Add a `TextField` that takes in the name of the expense, allowing the user to type with any characters.
   1. The placeholder of this should read "Expense Name"
2. Add a `TextField`that takes in the cost of the expense.
   1. The placeholder of this should read something similar to the above.
3. Add a `Button` that, when pressed, fires an `onClick` or similar function passed into the component to tell the parent component that a new entry was submitted with the corresponding `String`name and `Float`  amount.
   1. When you call the onClick, you should also afterwards clear the input of both text fields.
   2. It's okay if your code only works if the user has actually typed in a valid amount.
4. Add a nice title text to the top of it all!

Hints:

* If you make an @Preview to test out your component, you can actually RUN the preview and test typing into your component to see if it works! Just hit this button:

![](https://195521982-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LvOeSx5ZqjJA8sxykMu%2Fuploads%2Fz6irNMjzWiMM5Fmv2yMV%2Fimage.png?alt=media\&token=f49a8685-2fbd-4826-8a8d-cd9eb90eb93b)

Then, you can type into the text fields and even hit submit! (You won't see an OS keyboard; just type in with your computer keyboard. Make sure then to only type numbers into the other text field.)

</details>

<details>

<summary>MainScreen.kt (5pt)</summary>

iIn this file, you'll assemble everything together to make the app's screen!

1. Make a new file called MainScreen.kt with a corresponding @Composable.
2. Arrange the UI with a **LazyColumn** so that the header is on top, and several expense rows render underneath.
3. Whenever the user enters a new expense and clicks "Create", a new expense row should appear in the LazyColumn.
   1. This expense should have the same title, expense, and (some form) of representing the (system) time at which the expense was created.

Hints:

* You'll need a state to represent the list of expenses. When you update this state, the lazy column will auto-render new rows (Recomposition Lifecycle!)
* Use callback functions from the ExpenseHeader that will fire whenever the user enters/submits a new expense.
* For the system time, feel free to ask ChatGPT conceptual questions as to how to get the system time and convert it into a date-time readable format! Here's one possible way to do that:

```kotlin
// Get the current time as a number.
val currentTimeMillis = System.currentTimeMillis()

// Format it into a readable string.
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
    .withZone(ZoneId.systemDefault())
val formattedTime = formatter.format(Instant.ofEpochMilli(currentTimeMillis))
```

* You'll probably want to make some `data class`that represents an Entry. This `data class` would be called something like `Expense` and could have fields such as `title`, `dateTime`, and `cost`.
  * Then you can make a State that is a list of these expenses!

</details>

Make sure to also change the proper things in `MainActivity.kt` to actually get your screen to show when you run your emulator! Once finished, your app should look something like this:

{% file src="/files/jNRRO7IwO2BYURnWr12N" %}
Solution Demo Video!
{% endfile %}

When you're done, export your project through Android Studio and upload your ZIP to CMS! Congrats on finishing A2!

<figure><img src="https://195521982-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LvOeSx5ZqjJA8sxykMu%2Fuploads%2FZCdAyNpc9uGbXMmoZwzS%2Fimage.png?alt=media&amp;token=5db0f40e-28aa-44fb-a525-71df566d298e" alt=""><figcaption><p>Exporting with File -> Export -> Export to Zip File...</p></figcaption></figure>


# Week 3 | Navigation & Animations

The more complex sides of UI...

This week, we'll learn how to set up navigation between several screens and build small animations to give our UI a nice cherry on top!

This concludes the first half of the course. After week 3, you should be able to build any UI you can imagine!


# Relevant Links

**Google Slides:** [**\[Slides Link\]**](https://docs.google.com/presentation/d/1rKivLWa_h8R3dy_oOvEoowTxDw9QCIptk8dAJWoLdPw/edit?slide=id.ge39b6d51b7_0_34#slide=id.ge39b6d51b7_0_34)

**Relevant Textbook Sections**: [\[4.2 Implementation of the Bottom Navigation Bar\]](/chapters/8.-navigation/bottom-navigation), [\[2.4 Animations\]](/chapters/2.-jetpack-compose/2.4-animations)

**YouTube Recording**: [\[Week 3 Lecture Link\]](https://youtu.be/0W91vHFgkh4), [\[Week 3 Demo Link\]](https://youtu.be/dlgzr23WJ7Q)

**Demo Solution**: [\[GitHub Link\]](https://github.com/melissavelasquezz/Intro-to-android-demo-solutions/tree/main/demo3soln/app/src/main)


# Demo: Onboarding

So. many. screens!

This week, we're working on navigating between multiple screens via onClicks and the bottom navigation bar, as well as practicing different types of animations.&#x20;

This demo was loosely inspired by onboarding screens, which are typically used to set up a user's account the first time they use an app.&#x20;

We'll have three screens: one for a user to enter their name, one that displays a personalized welcome message, and a how-are-you page.

We'll also have a bottom navigation bar and buttons that will allow us to navigate between these screens!

<div><figure><img src="https://195521982-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LvOeSx5ZqjJA8sxykMu%2Fuploads%2Fye88MZkh98ZTqbCfZs7k%2Fdemo3.1.png?alt=media&amp;token=4550611c-1d57-4c26-bb36-06770b283232" alt=""><figcaption><p>1) EntryScreen</p></figcaption></figure> <figure><img src="https://195521982-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LvOeSx5ZqjJA8sxykMu%2Fuploads%2FASB7VntpQ4JNO7JKp8SG%2Fdemo3.2.png?alt=media&amp;token=8f7c4c0f-807b-4c19-976f-4383257214e6" alt=""><figcaption><p>2) WelcomeScreen</p></figcaption></figure> <figure><img src="https://195521982-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LvOeSx5ZqjJA8sxykMu%2Fuploads%2FAQtPI5bny0n5LjoQYYof%2Fdemo3.3.png?alt=media&amp;token=ddc78c7b-de93-40a5-b5e0-835d735775fb" alt=""><figcaption><p>3) MoodScreen</p></figcaption></figure></div>

### Starter Code:

Here's the [textbook's reference for how to set up Navigation](https://android-course.cornellappdev.com/chapters/8.-navigation/bottom-navigation).

Download, unzip, and open this file in Android Studio.

{% file src="/files/nmvlpF0Pk21KZHsaLC8d" %}

### Screen 1, 2: EntryScreen and WelcomeScreen

The functionalities of these screens are very reminiscent of the first demo: we want to be able to store user input and display it.

However, now that we are working across two different screens, the "ok" button will be responsible for updating the name in data as well as navigating to the next screen.&#x20;

We'll walkthrough setting up these screens, and they'll be built out for you in the starter code!

Run the project and click around in the emulator to get a general understanding of how the app works. You should be able to navigate between screens using the buttons on the screen and the bottom navigation bar.&#x20;

### Screen 3: MoodScreen

This screen displays four mood options and an emoji. The emoji and background color change according to the selected mood. Click around the MoodScreen UI to see how it works right now. Notice that the emoji and the background color change abruptly when the buttons are clicked.&#x20;

The starter code already gives you the Buttons and the emojis, so your job is to use animations to make the transitions look smoother.&#x20;

Remember the three types of animations introduced in lecture:

* animate\_AsState()
* Modifier animations
* AnimatedVisibility and AnimatedContent

For reference, here's what the final product should look like.

{% file src="/files/rnIWMRBc9ec8aKLUWH5P" %}
Demo
{% endfile %}

#### TODO 0: Add the navigation dependency

In the app `build.gradle` file, add the necessary dependency for navigation. Feel free to reference [docs](https://developer.android.com/develop/ui/compose/navigation#kts).

#### Todo 1: Create a NavController

#### Todo 2: Navigate to the selected screen

Hint: Use `item.screen`.

#### Todo 3: Navigate to WelcomeScreen

Hint: Look at `Screen.WelcomeScreen()`.

#### Todo 4: Add MoodScreen as a Destination

Follow the rest of the routes to navigate to `MoodScreen()`.

#### TODO 5: Animate the background color so that it fades into the new color when a different button is clicked.

This animation should last for a duration of 1000ms

* *Given that colors are defined on a continuous spectrum (in their hexcode forms), what type of animation should we use here?*
* *Remember to ask if you're unsure!*

TODO 5.5: The animation will handle updating the background color, so the starter code's update in the Button onClick will become redundant. Make sure to delete&#x20;

```kotlin
backgroundColor = getMoodColor(mood)
```

from the onClick before attempting to build your project.

<details>

<summary><em>Hint: Code structure for background color animation</em></summary>

```kotlin
val backgroundColor by animateColorAsState(
    targetValue = //...
    animationSpec = //...
)
```

</details>

#### **TODO 6: Animate the emoji so that when a different mood button is clicked, the emoji appears to slide horizontally out of view while the next emoji slides horizontally into view from the other side.**

The slide-in animation and slide-out animation should each take a duration of 300ms

* *This syntax can be tricky so make sure to google things and ask questions*&#x20;
* *Given that the emoji changes depending on which mood is chosen, which of the three main types of animations should we use here?*
* *Feel free to take creative liberties with what colors, emojis, and moods you pick!*

<details>

<summary><em>Hint: Code structure for emoji animation</em></summary>

```
AnimatedContent(
            targetState = //...
            transitionSpec = {
                //slide in
                togetherWith
                //slide out
            }
        ) { mood ->
            Text(
                text = getMoodEmoji(mood.first),
                fontSize = 200.sp
            )
        }
```

</details>

### Congrats you've finished the demo!


# A3: Stock Trading (RobbingGood)

I am Sobbin...hood...

{% hint style="info" %}
This assignment has a release code! Please download it below, extract it, and open it in Android Studio. Ensure it can compile and run fine on your emulator before starting this assignment.
{% endhint %}

{% file src="/files/d4L70RNwyjMeWkwOrzTC" %}
A3 Release Code! Extract and Open as an Android Project.
{% endfile %}

For our second assignment, we'll be making a stock-trading app competitor! Of course we won't be working with real money, but we've set up the above release code to give the semblance of trading your hard-earned cash!&#x20;

Let's walk through the code, shall we?

*NOTE: This is a longer assignment, so we made sure to provide lots of **Hints!***

## Release Code:

Expand each of the files below for a description of the release code!

<details>

<summary>WalletRepository.kt</summary>

This repository manages the user's virtual wallet. It keeps track of the current balance and allows the user to deposit or withdraw money when buying or selling stocks. Additionally, it maintains a record of the stocks the user owns.

Methods you will use:

`getBalance()`: Gets a numeric value for the balance that you can freely use in your UI!

`percentageBalanceChange()`: Returns a float representing the percentage that the user's wallet has recently changed by (rise is positive, fall is negative). Free to use in UI.

`buyStock()`: Buys a stock.

`sellStock()`: Sells a stock.

`amountStockOwned()`: Tells you how many of a given stock you own. Free to use in UI.

**YOU SHOULD NOT EDIT THIS FILE.**

</details>

<details>

<summary>StockRepository.kt</summary>

This repository handles the dynamic pricing of stocks, where each stock is identified by a ticker symbol. It provides methods to retrieve the current price of a stock based on its ticker.&#x20;

Behind the scenes, it randomly updates the stock prices every 3 seconds to simulate market fluctuations.

Methods you will use:

`getStockPrice()`: Returns the (current) stock price of the given ticker that you can use in your UI.

`increaseOrDecreaseStockPrice()`: Returns a boolean; if true, the specified stock has increased in price. If false, it has decreased. Can freely use in your UI.

*Note: do NOT use \`readStockPrice()\`. The way it is implemented will not work in your UI.*

**YOU SHOULD NOT EDIT THIS FILE.**

</details>

<details>

<summary>MainScreen.kt</summary>

MainScreen is an incomplete main screen that shows the home for our app, RobbingGood!

It consists of a place where your `WalletRow` and `TickerRow`s will go!

</details>

{% hint style="danger" %}
*Note: These two Repository files violate the MVVM paradigm. They are NOT a good example for what to do for the **Hack Challenge**; please refer to lectures 4, 5, and 6 for MVVM practices. These files are simply a bare minimum functionality we provide for this assignment.*
{% endhint %}

## Requirements:

Okay, onto the assignment!

(expand each file below to see your requirements!)

<details>

<summary>WalletRow.kt</summary>

WalletRow is a component that shows the user's balance with animations. If the user's balance has recently risen, the text shows green, otherwise red, and a little animation plays to swap the text out.

In general, try to match this UI:

![](https://195521982-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LvOeSx5ZqjJA8sxykMu%2Fuploads%2FzPY2IjoM9r1eMiqvl3uT%2Fimage.png?alt=media\&token=9cbfbb9e-48c2-4db1-859b-57a3a57b8a0f)

1. Display the user's wallet balance given by `getBalance()` as a nicely formatted ($XXX.XX), along with its percentage change (`percentageBalanceChange()`) to the far right, 2 decimal places max.
2. Whenever the balance changes, an `AnimatedContent` should swap out the percentage text.
3. If the percentage is positive, the percentage text should be a (bold) green, otherwise a (bold) red.
4. Once you've made this component, WalletRow should be replaced on the `MainScreen`, and you'll also later use it in `StockDetailScreen`.

Hints:

* Given a percentage, you can use the following code snippet to convert it into a nicely readable string:

```kotlin
// Format 2 digits after the decimal
val formattedPercentage = "%.2f".format(abs(percentage * 100f))
val percentageString = if (percentage < 0) {
    "(-$formattedPercentage%)"
} else {
    "(+$formattedPercentage%)"
}
```

* To achieve the space in between the left and right parts of the row, you might want to use `Arrangement.SpaceBetween`.

</details>

<details>

<summary>TickerRow.kt</summary>

TickerRow is a UI component that displays a quick and dirty summary of a stock's price and which allows the user to click on the row to navigate to the Stock Detail Screen (SDS).

In general, try to match this UI:

![](https://195521982-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LvOeSx5ZqjJA8sxykMu%2Fuploads%2Fpyr6gA90yctHwxcS1Jzd%2Fimage.png?alt=media\&token=df48abf6-0f44-4ceb-8bef-f4f89de1681b)

You&#x20;

1. Similar to WalletRow, display the balance and percentage change of this stock.
   1. Whenever the stock price changes, an `AnimatedContent` should swap the balance/percentage text out.
   2. Green if rise, red if fall.
2. If the user taps on this row, they should navigate to the SDS, sending the ticker as a navigation argument.
3. Once you've made TickerRow, use it in the LazyColumn in `MainScreen` to show the tickers!

Hints:

* You'll have to set up a NavHost! Please refer to our demo this week for tips.
  * (!!!) Make sure to then call your navigation code in `MainActivity` — it's currently just calling `MainScreen()`.
  * Do NOT make the NavHost inside of `MainScreen()` — the nav host should exist outisde of main screen, and then have main screen as one of the routes, and SDS as another route.
* Think about what the state should be for the `AnimatedContent`--there are multiple correct answers!
  * For example, you can say that the text and color is a state! Or that the price/percentage is a state, and then you calculate the text with those values inside the `AnimatedContent`body. As long as it looks like it's animating right, you're good!
* You can reuse snippets of code from `WalletRow`and A2 to achieve the money text formatting.
* Use `Modifier.clickable()` to make *any* Composable clickable.
  * You'll likely want to thus provide an `onClick: () -> Unit` as an argument to `TickerRow.kt`.

</details>

<details>

<summary>StockDetailScreen.kt</summary>

The Stock Detail Screen is a full screen which displays the current price of the selected stock, the user's wallet, and the amount of that stock owned. It also provides the option to buy or sell instances of that stock, updating the user's wallet accordingly.

In general, try to match this UI:

![](https://195521982-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LvOeSx5ZqjJA8sxykMu%2Fuploads%2FwurojbitUgd0Kx2LP3Wo%2Fimage.png?alt=media\&token=1b004629-3a09-4a1f-b951-7c95ad6e6dc9)

1. The ticker that the user has selected (and given to this screen as a nav argument) should be displayed at the top of this page as the title.
2. In the (roughly) middle of the screen, there should be 2 large, bold texts— one for the balance, and one for the percentage.
   1. These should animate differently from the others; The float balance and percentage here should **animate** to the correct balance (meaning the literal number itself animates).
   2. These texts can just be black.
3. The background of the entire screen should **animate** to a very light green if the stock is up, or a light red if the stock is down.
   1. You must use `animateColorAsState` to achieve a smooth color animation.
4. At the footer of the screen, you should use `WalletRow` to display the user's current balance.
5. Add a text of some sort to display how many of the current stock the user has. You may use `amountStockOwned()`.
6. Add two buttons, Buy and Sell. Buying should call `buyStock()`and Selling should call `sellStock()`.

*Note: To navigate back to the home screen, just use the in-built navigation. If you don't see nav buttons, on android, that just means dragging and letting go from the left-hand of the screen.*

***Hints:***

* You'll have to provide the stock ticker as a navigation argument. See lecture for to how to do this!
* To achieve the faded green/red color, you can leverage doubling up a background Modifier! The following code will first make the background White, then layer a faded (0.2 opacity) color on top (for some `backgroundColor`):

```kotlin
Modifier.background(Color.White)
.background(backgroundColor.copy(alpha = 0.2f)),
```

* See `animateFloatAsState`and `animateColorAsState`.
* You might want to take in the stock ticker (string) as an argument to the  `StockDetailScreen` composable function!
  * (Similarly, you might want to take in the nav controller as an argument to `MainScreen` so that `MainScreen` knows how to navigate.)

</details>

Once you've completed A3, your solution should look something like this:

{% file src="/files/pKFjMQPYyjmokvo8t435" %}
A3 Solution Demo! (Fixed 3/26/25)
{% endfile %}

When you're done, export your project through Android Studio and upload your ZIP to CMS! Congrats on finishing A3!

<figure><img src="https://195521982-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LvOeSx5ZqjJA8sxykMu%2Fuploads%2FZCdAyNpc9uGbXMmoZwzS%2Fimage.png?alt=media&amp;token=5db0f40e-28aa-44fb-a525-71df566d298e" alt=""><figcaption><p>Exporting with File -> Export -> Export to Zip File...</p></figcaption></figure>


# Week 4 | MVVM and Flows

How do you make an app... an actual app?

This week, we'll learn to leverage the MVVM design pattern with flows to connect several parts of our app, all with the goal of allowing real, useful app logic, starting with the View Model layer!


# Relevant Links

**Google Slides:** [\[Slides Link\]](https://docs.google.com/presentation/d/1TpJ3zKXDli7aK4lD972AwPwouDy8S-J8HbcODcOMSlw/edit?usp=sharing)

**Relevant Textbook Sections**: [\[7. MVVM Design Pattern\]](/chapters/7.-mvvm-design-pattern), [\[8. Flows\]](/chapters/8.-flows)

**Lecture Recording**: [\[Week 4 Lecture\]](https://www.youtube.com/playlist?list=PLjf6nsEcF5KP5cZTjZ3HwObzQ9jTsU2X4), [\[Week 4 Demo\]](https://youtu.be/Vv_C6HD_KR8)

**Demo 4 Solution**: [\[GitHub\]](https://github.com/melissavelasquezz/Intro-to-android-demo-solutions/tree/main/demo4soln)


# Demo: Eatery Card 2

EateryCard Part 2

In this week's demo, you'll be practicing the MVVM architecture by displaying data from a ViewModel in the View and implementing some filtering logic in the ViewModel of a pseudo-Eatery app!&#x20;

<figure><img src="https://195521982-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LvOeSx5ZqjJA8sxykMu%2Fuploads%2FFaEIJ5aFuR6w825bVbB7%2Fdemo4.png?alt=media&amp;token=0a48f34d-a447-4d2f-9cf8-35a83e16d5f6" alt="" width="188"><figcaption></figcaption></figure>

Download the starter code:

{% file src="/files/jx6RrGQ78k0skT7XYTh7" %}

## Part 1: Hilt Setup

(Hilt works together with Navigation to inject dependencies into the NavGraph, so you'll also have to set up Navigation whenever you're using Hilt. We've already done that for you in the starter code though!)

### Step 1: Add necessary gradle dependencies

1. Add the `hilt-android-gradle-plugin` plugin to your project's root `build.gradle` file and hit Sync Now on the blue banner that pops up:

{% code title="build.gradle.kts (Project: demo4\_starter2)" %}

```kotlin
plugins {
  ...
  id("com.google.dagger.hilt.android") version "2.57.2" apply false
  id("com.google.devtools.ksp") version "2.2.10-2.0.2" apply false
}
```

{% endcode %}

2. Apply the Gradle plugin and add these dependencies in your `app/build.gradle` file and Sync Now:
   1. (Some of the dependencies in the following code block depend on each other, so any red highlighting should be resolved during the sync.)

{% code title="build.gradle.kts (Module: app)" %}

```kotlin
plugins {
  ...
  id("com.google.dagger.hilt.android")
  id("com.google.devtools.ksp") version "2.2.10-2.0.2"
}

dependencies {
  ...
  implementation("com.google.dagger:hilt-android:2.57.2")
  ksp("com.google.dagger:hilt-android-compiler:2.57.2")
  implementation("androidx.hilt:hilt-navigation-compose:1.3.0")
}
```

{% endcode %}

### Step 2: Make a MainApplication class

In the same directory as MainActivity.kt, create a MainApplication.kt file and add the following code to it. Do imports as needed!

```kotlin
@HiltAndroidApp
class MainApplication : Application() {}
```

Add the following to your AndroidManifest.xml to define your custom Application class. This is required for Hilt to work properly:

```kotlin
<application
        android:name=".MainApplication"
        ...
```

### Step 3: Annotate and Inject

Now that Hilt is set up, it can provide dependencies to other Android classes with the @AndroidEntryPoint annotation. For our purposes, we want to annotate the MainActivity (in MainActivity.kt) to let Hilt know that it is working on MainActivity.&#x20;

```kotlin
//example
@AndroidEntryPoint
class MainActivity : ComponentActivity() {...}
```

You now need to Inject into ViewModels and Repositories as well. In the ViewModel we have done this for you. Take a look at the header there and try it out in the EateryRepository.kt.

{% code expandable="true" %}

```
// *hint* this is what the new repository header should be

class EateryRepository @Inject constructor() {
```

{% endcode %}

## Part 2: Connecting the View and ViewModel

### 1. Set up the EateryViewModel

&#x20;Go to the EateryViewModel.kt file and uncomment it.

That's all for now, we'll come back to these files!

*Keep uncommenting code as you work, a lot was commented to start because a lot of it depends on Hilt.*

### 2. Set up the View

If you look over the starter code in EateryScreen, you'll notice we have two composables.

* EateryScreen is our main UI screen for this project.
* EateryScreenContent will contain the FilterBar and the EateryCards we're displaying.
  * *Notice that EateryScreenContent takes a few functions as arguments. Since the viewModel will provide these functions, separating our content into two nested composables is just a layer of abstraction that helps us keep our top-level function calls cleaner. This will make more sense as we get further through the demo.*&#x20;

Lets start at the EateryScreenContent!

#### EateryScreenContent

(Uncomment the parameter uiState and the FilterRow component and import EateryUIState before you start! Complete their TODOs!)

1. Make a LazyColumn that displays EateryCards for all of the filteredEateries in the EateryRepository.&#x20;
   1. *Hint: The type of the uiState is EateryUIState. It has a field called filteredEateries. Try using that, and review the LazyColumn syntax from previous demos/assignments if needed!*
   2. *Hint: you can use verticalArrangement  = Arrangement.spacedBy(10.dp) as an argument in LazyColumn to achieve spacing between each card.*
2. Pass in the appropriate ui state fields into the FilterRow component.

Uncomment the EateryScreenContentPreview() and check to see that the EateryCards and filter row appear correctly.

Now we can finish the EateryScreen composable!

#### EateryScreen

We want to be able to call the EateryScreenContent composable with real data to actually display our EateryCards. For that, we need to pass two parameters into EateryScreenContent: uiState and onLocationSelected.&#x20;

1. We need to get the uiState from the ViewModel.&#x20;
   1. Pass the EateryViewModel into the EateryScreen as an argument. We also need to specify that the EateryViewModel is a hiltViewModel. This should look something like

```kotlin
viewModel: EateryViewModel = hiltViewModel()
```

2. Now we want to access the data we need to display, which is an instance of the EateryUIState that is defined in the EateryViewModel.&#x20;

```kotlin
val uiState by viewModel.uiState.collectAsStateWithLifeCycle()
```

3. Finally, the EateryScreenContent needs to know what to do when a location filter is pressed. Follow the todo in the code to pass in a function that was defined in the EateryViewModel!
   1. *Hint: Remember that we're using an instance of the EateryViewModel, viewModel, that's being passed in as an argument. So to use an EateryViewModel function, we need to call it from viewModel.someFunction()*
4. Since we haven't defined a function for onNowOpenSelected, go ahead and keep the original empty lambda {} from the starter code for that argument of EateryScreenContent.

## Part3: Implement the nowOpen filter

To get a bit of experience working in the ViewModel, we'll have you create the  nowOpen filter, which just returns all eateries with isOpen = true.&#x20;

Follow the todos in EateryViewModel.kt and use the locationFilter as a model!

Extra hints:

* Todo 1:&#x20;
  * .filter (from allEateries.filter()) runs each eatery through the series of conditionals in its body. If the final evaluation is true, then the eatery is included in filteredEateries, otherwise it's not included.&#x20;
  * For the filtering conditional, it should only be true if the toggle filter is inactive OR the eatery isOpen field is true.
    * Add your filtering logic to the existing block using && (double ampersand)
* Todo 2:
  * This is almost exactly the same as onLocationSelected. Can you figure out what we're updating in .copy?

Now you can run your emulator and test that your 'Now Open' filter is working!

### Congrats, you've finished demo 4!


# A4: Chat of a Lifetime

Asking the real questions in life!

This assignment has a release code! Please download it below, extract it, and open it in Android Studio. Ensure it can compile and run fine on your emulator before starting this assignment.

{% file src="/files/i2oh8MlRalexeMnjbNx0" %}
A4 Release Code!
{% endfile %}

For our third assignment, we'll have you implement the ViewModel to connect up a mock chat app! Instead of chatting with another real person (as that would require a lot of real, complex networking), we'll have you chat with a mini pocket *philosopher* on your phone!

*We've had enough assignments about money at this point, haven't we? And money isn't happiness, after all...*

Let's walk through the code, shall we?

## Release Code:

Expand each of the files below for a description of the release code!

<details>

<summary>ChatRepository.kt</summary>

A Model class that performs (mock) networking tasks to implement that chatbot/chat functionality.

DO NOT EDIT THIS FILE.

You may interface with this file, though, using the following properties, to complete (mock) networking:

* `chatMessageFlow`: A flow emitting a list of (all) the chat messages in the history of the chat.
* `currentlyTypingFlow`: A flow emitting (as a Boolean) whether the chatbot is "typing" or not.
* `botNameFlow`: A flow emitting the name of the chat bot.
* `sendMessage(text: String)`: A mock networking function that will send the user's `text` message as a chat message.

</details>

<details>

<summary>ChatViewModel.kt</summary>

A (Hilt) ViewModel class that connects up `ChatRepository.kt` and `ChatScreen.kt`.

You'll mostly be working in this file for your solution. This file is VERY incomplete.

For starters though, we've given you...

* `uiStateFlow`: The (only) state flow that your VM should use to emit state down to the UI. Emits an object of type `ChatUiState`.
* `ChatUiState`: A data class representing the UI state that contains ALL the data that `ChatScreen.kt` needs to know. This class is currently incomplete; you'll need to add fields here as you go.

</details>

<details>

<summary>ChatScreen.kt</summary>

The primary (and only) UI screen in this assignment. It consists of a header, a body consisting of chat messages in a big `LazyColumn`, and a footer where the user can type in a message and send.

For organization, this file consists of several `@Composable`s:

* `ChatScreen()`: The main screen Composable.
* `ChatFooter()`: The footer, where the text field and send button lay.
* `ChatHeader()`: The header, where the chat bot's name and last sent text lay.

There happens to be a `@Preview` here for the header; take a look if you'd like!

</details>

There are a few other files here, but those are not too important and are mostly complete:

* `ChatBubble.kt` is just a component for the Chat Bubble.&#x20;
* Other files are basically junk; don't worry about them.

***

## Requirements:

Okay, onto the assignment!

This assignment is a bit different from previous assignments in that you'll be implementing **features**, one by one. The reason we do not split then into files is because your solution will often have to span several files for each feature.

(expand each of your features' requirements below!)

<details>

<summary>1) Reading the Message Flow</summary>

First, let's connect our chat up with our pocket philosopher! It wants to say some stuff to us right now, but we can't quite hear it just yet until we connect our VM to our Model repository!

1. Inject an instance of `ChatRepository.kt` into our ViewModel.
2. Read the flow value of the chat messages flow from the repository, using it to update (with a `copy`) the current Ui State Flow with the correct messages!

For this section, we've **already hooked up the messages field from the UiState to the UI**. So this should be all you need to do!

Note: If you've done this correctly, you should no longer see the initial test messages, and should now see the following chat:

![](https://195521982-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LvOeSx5ZqjJA8sxykMu%2Fuploads%2Fz9YjkGFDilYwavtbmwb0%2Fimage.png?alt=media\&token=c6310019-c678-4cd4-849b-8ef9f870c2ef)

Seems like our philosopher is trying to reach us! Let's finish the next parts so we can have a nice, deep chat :-)

</details>

<details>

<summary>2) Typing into the Text Field</summary>

If you currently try typing into the text field in the footer, nothing changes, and the text currently freezes at "TODO". Let's fix this!

1. Add a new field to `ChatUiState` corresponding to the text currently typed into the text field.&#x20;
2. Pipeline this down the the UI such that the UI now reads from this text and puts it through the `ChatFooter()` into the `BasicTextField` value.
3. Now time for the reverse direction! Add a new function `onTextChanged(text: String)` to your VM. Make it so that when you type text and the `BasicTextField` value changes, this VM method is called!

Hints:

* You'll need to add new arguments to `ChatFooter()`!
* Ideally, 2 new arguments: one for the text downwards, and one for the onTextChanged upwards!

You should now be able to *type* a message into the box. But you can't quite send it yet...

</details>

<details>

<summary>3) Sending a Message</summary>

Now let's allow ourselves to actually send a message to our philosopher!

1. Change the derived field value `isSendEnabled` such that if the typed text (field in your ChatUiState from part 2) is non empty, it is true, otherwise false.&#x20;
   1. *If you've done this correctly, the Send button should now turn enabled if you've typed in text!*
2. Add an `onSend()` that gets called by the Button `onClick` in ChatFooter. You'll need to pipeline this onClick upwards, similar to what you did in part 2.
3. Implement `onSend()` :
   1. Send the (typed in) message by calling `chatRepository.sendMessage(...)` .&#x20;
   2. Clear the typed in message after the message is sent.

You should now be able to send a message and see your philosopher respond! In fact, you should actually be able to converse with your philosopher now. Try it out!

</details>

<details>

<summary>4) Reading the Header Flow Information</summary>

Okay, nearly there! But the name we've given our philosopher isn't quite showing up top! And our chat isn't keeping track of when our last message was sent... let's fix that!

1. Similarly to previous parts, hook up ChatRepository's `botNameFlow` down through the Repository, to the VM, to the UI, so that the TODO text now reads the bot's name!
   1. *It should be ??? at first, then populate with your name you've given after you give the philosopher a name.*
2. Do the same for `timestampFlow` such that the "Last message sent at: TODO" now reads the correct time!

(A lot of working with MVVM is going through these motions. After this, we just have one more and you'll be an expert!)

Here's an example of what you should see after you name your philosopher:

![](https://195521982-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LvOeSx5ZqjJA8sxykMu%2Fuploads%2FOZFSGMPPRATc0OcCViRK%2Fimage.png?alt=media\&token=fcf98eda-513d-4088-be86-39ef74218bfc)

</details>

<details>

<summary>5) Reading the Currently Typing Flow</summary>

Finally for a finishing touch! Your philosopher often pauses and takes time to think, but also takes time to type out a response! It'd be great if you could have a little indicator for that...

Thankfully, your UI already has this! See the TODO for part 5) in ChatScreen?

1. Read from `currentlyTypingFlow` from the chat repository (down through the VM, to the UI) to put the correct boolean condition for if the philosopher is typing (instead of just currently FALSE).

</details>

If you've solved A4 correctly, your solution should look something like this...

{% file src="/files/Xip0CHoDHroXMcLu5pLk" %}
A4 Soln Demo Video!
{% endfile %}

When you're done, export your project through Android Studio and upload your ZIP to CMS! Congrats on finishing A4!

<figure><img src="https://195521982-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LvOeSx5ZqjJA8sxykMu%2Fuploads%2FZCdAyNpc9uGbXMmoZwzS%2Fimage.png?alt=media&amp;token=5db0f40e-28aa-44fb-a525-71df566d298e" alt=""><figcaption><p>Exporting with File -> Export -> Export to Zip File...</p></figcaption></figure>

*Note: Perhaps you find it annoying that you have to scroll with each message manually? We'll learn a solution for that next week with UIEvents!*


# Week 5 | Coroutines, Networking, JSON

The backend to my frontend ❤️

In this week, we'll learn how to write networking requests with Retrofit, leveraging coroutines and JSON specification to read important data from an example backend!


# Relevant Links

**Google Slides:** [\[Slides Link\]](https://docs.google.com/presentation/d/1MITDAKcLD7efTiEF_XIzQyeUj-tzrLuw4hZ2xSDumT8/edit?usp=sharing)

**Relevant Textbook Sections**: [\[5.5 Concurrency\]](/chapters/5.5-concurrency), [\[6. Networking and 3rd Party libraries\]](/chapters/6.-networking)

**YouTube Recordings**: [\[Week 5 Lecture\]](https://youtu.be/8g999efx5i8), [\[Week 5 Demo\]](https://youtu.be/roAnjZFO5eo)

**Demo Solution**: [\[GitHub Link\]](https://github.com/melissavelasquezz/Intro-to-android-demo-solutions/tree/main/demo5soln/app/src/main/java/com/cornellappdev/chatter)


# Demo: Chatter

Also known as Y...

This demo has starter code! Please download it below, extract it, and open it in Android Studio. Ensure it can compile and run fine on your emulator before starting this assignment.

{% file src="/files/PNCT8CxloMXCOU3oK0am" %}
Demo 5 Release Code!
{% endfile %}

For this demo, we'll have you develop the networking logic for a basic social media app!

Let's walk through the code, shall we?

***

## Starter Code

Expand each of the files below for a description of the starter code!

<details>

<summary><code>data</code> package</summary>

Contains all code that deals with data directly (data layer)

* `model` contains the data classes that will be used to represent the Post data
* `remote` contains the Post API Service interface
* `repository` contains the `PostRepository` that will be used to handle and expose Post data to the UI layer.

</details>

<details>

<summary><code>di</code> package</summary>

Contains all code that deals with dependency injection

* `ChatterApplication` contains the Hilt Application Class for the app
* `NetworkModule` contains the providers for the various parts of the retrofit layer like the OkHttpClient

</details>

<details>

<summary><code>ui</code> package</summary>

Contains all code in the UI Layer including ViewModels

* `components` contains a couple Composables that will be used in the `PostScreen`
  * `ChatterHeader` contains the top part and search bar of the screen
  * `PostCard` contains the component that represents the Post data as a card
* `screens` contains the `PostScreen` that displays the products as a list and uses a search bar to filter the results
* `viewmodels` contains the `PostViewModel` that handles the business logic of `PostScreen`

</details>

***

## Instructions:

<details>

<summary>1) OkHttpClient</summary>

Create the OkHttpClient provider in `NetworkModule`

</details>

<details>

<summary>2) Retrofit Instance</summary>

Create the Retrofit instance provider. For our base url, we will be using <https://dummyjson.com/>.   &#x20;

</details>

<details>

<summary>3) PostAPIService</summary>

We will be using the Get All Posts and Search Posts endpoints from [here](https://dummyjson.com/docs/posts#posts-all). Based on that information, do the following:

1. Create the relevant serializable data classes in `Post.kt` to represent the JSON responses. To see the example JSON output, you can visit <https://dummyjson.com/posts> for example.&#x20;
   1. It might be helpful to have AI do the heavy lifting for creating the data classes with serial name.
2. Define the methods that represent the API endpoints we are using with the correct Retrofit annotations in `PostApiService.kt`
3. Create the API Service instance in `NetworkModule` using the retrofit instance provider you made earlier and the interface you just defined.

</details>

<details>

<summary>4) PostRepository.kt</summary>

1. Inject the api service into the repository.
2. Implement the `getAllPosts` method by using the api service and returning a Result\<List\<Post>>.
3. Do the same for the `searchPosts` method.

</details>

<details>

<summary>5) PostViewModel.kt</summary>

1. Inject the repository into the ViewModel.
2. Replace Any with the actual Post data class you defined earlier in the `PostUiState`.
3. Implement `loadPosts` with the repository functions. Remember to handle the loading and error states accordingly when the function is called, on success of the repository function result, and on failure of the repository function result.
4. Do the same for `searchPosts`.

</details>

<details>

<summary>6) PostScreen.kt</summary>

Alright, time to tie it all together! In this part, you'll connect your screen to the ViewModel and test the final networking logic to get your app actually showing the proper response from the backend.

1. Display the list of products in the `PostContent` composable using `LazyColumn` and the `PostCard` composable.
2. Collect the uiState and searchQuery StateFlows from the ViewModel.&#x20;
3. Pass the states and the relevant handlers into the `PostScreenContent` composable which you should add to the `PostScreen` composable.

If you get any networking errors, make sure to debug with LogCat errors!

</details>

If you've solved the demo correctly, your solution should look something like this...

{% file src="/files/T2QyOZnkdraBmlz9tYa6" %}


# A5: Bananazon

I'm going bananas!!!

This assignment has a release code! Please download it below, extract it, and open it in Android Studio. Ensure it can compile and run fine on your emulator before starting this assignment.

{% file src="/files/vLFZU5eqRj3fLjDqeRvB" %}
A5 Release Code!
{% endfile %}

For our fifth assignment, we'll have you develop the networking logic for a basic e-commerce app!

*We've talked about money and happiness, and now, let us ponder — can money buy happiness? Perhaps not, but money sure can buy a lot of things — let's dive into the depths of materialism...*

Let's walk through the code, shall we?

***

## Release Code

Expand each of the files below for a description of the release code!

<details>

<summary><code>data</code> package</summary>

Contains all code that deals with data directly (data layer)

* `model` contains the data classes that will be used to represent the Product data
* `remote` contains the Product API Service interface
* `repository` contains the `ProductRepository` that will be used to handle and expose Product data to the UI layer.

</details>

<details>

<summary><code>di</code> package</summary>

Contains all code that deals with dependency injection

* `BananzonApplication` contains the Hilt Application Class for the app
* `NetworkModule` contains the providers for the various parts of the retrofit layer like the OkHttpClient

</details>

<details>

<summary><code>ui</code> package</summary>

Contains all code in the UI Layer including ViewModels

* `components` contains a couple Composables that will be used in the `ProductScreen`
  * `Header` contains the top part and search bar of the screen
  * `ProductCard` contains the component that represents the Product data as a card
* `screens` contains the `ProductScreen` that displays the products as a list and uses a search bar to filter the results
* `viewmodels` contains the `ProductViewModel` that handles the business logic of `ProductScreen`

</details>

***

## Requirements:

This assignment is not split into features, but rather more so as steps towards implementing Retrofit correctly.

<details>

<summary>1) OkHttpClient</summary>

Create the OkHttpClient provider in `NetworkModule`

</details>

<details>

<summary>2) Retrofit Instance</summary>

Create the Retrofit instance provider. For our base url, we will be using <https://dummyjson.com/>.   &#x20;

</details>

<details>

<summary>3) ProductAPIService</summary>

We will be using the Get All Products and Search Products endpoints from [here](https://dummyjson.com/docs/products#products-all). Based on that information, do the following:

1. Create the relevant serializable data classes in `Product.kt` to represent the JSON responses. To see the example JSON output, you can visit <https://dummyjson.com/products> for example.&#x20;
   1. It might be helpful to have AI do the heavy lifting for creating the data classes with serial name.
2. Define the methods that represent the API endpoints we are using with the correct Retrofit annotations in `ProductApiService.kt`
3. Create the API Service instance in `NetworkModule` using the retrofit instance provider you made earlier and the interface you just defined.

</details>

<details>

<summary>4) ProductRepository.kt</summary>

1. Inject the api service into the repository.
2. Implement the `getAllProducts` method by using the api service and returning a Result\<List\<Product>>.
3. Do the same for the `searchProducts` method.

</details>

<details>

<summary>5) ProductViewModel.kt</summary>

1. Inject the repository into the ViewModel.
2. Replace Any with the actual Product data class you defined earlier in the `ProductUiState`.
3. Implement `loadProducts` with the repository functions. Remember to handle the loading and error states accordingly when the function is called, on success of the repository function result, and on failure of the repository function result.
4. Do the same for `searchProducts`.

</details>

<details>

<summary>6) ProductScreen.kt</summary>

Alright, time to tie it all together! In this part, you'll connect your screen to the viewmodel and test the final networking logic to get your app actually showing the proper response from the backend.

1. Display the list of products in the `ProductsContent` composable using `LazyColumn` and the `ProductCard` composable.
2. Collect the uiState and searchQuery StateFlows from the ViewModel.&#x20;
3. Pass the states and the relevant handlers into the `ProductsScreenContent` composable which you should add to the `ProductsScreen` composable.

If you get any networking errors, make sure to debug with LogCat errors!

</details>

If you've solved A5 correctly, your solution should look something like this...

{% file src="/files/YaD7JGG97cXXtqnsI4pU" %}
A5 Solution Demo Video!
{% endfile %}

When you're done, export your project through Android Studio and upload your ZIP to CMS! Congrats on finishing A5!

<figure><img src="https://195521982-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LvOeSx5ZqjJA8sxykMu%2Fuploads%2FZCdAyNpc9uGbXMmoZwzS%2Fimage.png?alt=media&amp;token=5db0f40e-28aa-44fb-a525-71df566d298e" alt=""><figcaption><p>Exporting with File -> Export -> Export to Zip File...</p></figcaption></figure>


# Week 6 | Dumb Components & UIEvents

How do we make our components obey MVVM? Make them dumber!

In the last week of the course, we'll learn how to perfect our View layer of Model-View-ViewModel to allow any part of our app to communicate with our UI effectively!

This concludes the second half of the course. After this, you should have everything you need to build a fully functional, useful app!


# Relevant Links

**Google Slides:** [\[Slides Link\]](https://docs.google.com/presentation/d/1QqpvnXvUweqFmZLiYSBbzFrHL9qzPgLUG2OIq54py3o/edit?usp=sharing)

**Guest Lecture Slides**: [\[Slides Link\]](https://docs.google.com/presentation/d/1NMdE9rA0yALnvfi82T5MILChY6JmVRJ_mmsXGVq2dy4/edit?usp=sharing)

**Relevant Textbook Sections**: [\[Bonus Lecture: Industry Practice\]](https://app.gitbook.com/o/-LDzut-Pw60HMkLc_mHP/s/-LvOeSx5ZqjJA8sxykMu/~/changes/896/archive/fall-2024-course-material/bonus-lecture-industry-practice)&#x20;

*(^ The slides of this lecture greatly communicate the same idea as this week's material.)*

**Lecture Recording**: [\[Week 6 Lecture\]](https://youtu.be/WHZXK19injs)

**Demo Recording (from SP25)**: [\[Week 6 Demo\]](https://youtu.be/Ri6oLJTIj20)


# Demo: Music Player

This week, we'll be practicing more MVVM abstractions and UIEvents.

This app starts at the MiniPlayerScreen. When we click on the MiniPlayerCard, we are taken to LoadingScreen. There's a slight delay as our next screen "loads" before the app navigates there.

<div><figure><img src="https://195521982-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LvOeSx5ZqjJA8sxykMu%2Fuploads%2FQGH9IIawiIBcnpxYUW0E%2Fdemo5_1.png?alt=media&amp;token=cabd5417-2a78-4946-95ff-895f0785d137" alt=""><figcaption><p>Screen 1</p></figcaption></figure> <figure><img src="https://195521982-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LvOeSx5ZqjJA8sxykMu%2Fuploads%2Ff3vbr5Hk7klZMfuCpMyl%2Fdemo5_4.png?alt=media&amp;token=6212f9ff-5ac3-474e-aae2-bbd6683baa34" alt=""><figcaption><p>Screen 3</p></figcaption></figure> <figure><img src="https://195521982-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LvOeSx5ZqjJA8sxykMu%2Fuploads%2FE0Xi3oYjTnR8WafSiL1k%2Fdemo5_3.png?alt=media&amp;token=06ae29b7-3ff1-4288-9c34-6833a023b977" alt=""><figcaption><p>Screen 2</p></figcaption></figure></div>

Download the starter code:

{% file src="/files/JLl2ISQGW2TRoezh1v16" %}

## Part 1: Implement LaunchedEffects for Navigation

Remember that LaunchedEffects define a function that will be called when a certain value changes (the 'certain value' that we want LaunchedEffect to keep an eye on here is navigateEvent).&#x20;

In LoadingScreen.kt, you'll see that there's already a LaunchedEffect. You'll need to make the **first** LaunchedEffect before that, which actually does the navigation.

1. The first LaunchedEffect will listen for a navigation event and perform the navigation.
2. The second LaunchedEffect starts the process (detailed in the function call) that will eventually produce the navigation event.&#x20;

<details>

<summary>LaunchedEffect syntax can be a little tricky so here's a hint!</summary>

```
LaunchedEffect(__________) {
    _________?.consumeSuspend { destination ->
        __//navigate to destination______________
    }
}
```

</details>

Once you've correctly implemented the LaunchedEffect, clicking on the MiniPlayerCard should take you to the LoadingScreen and then the PlayerCardScreen after a short delay.

## Part 2: Make the PlayerCard a dumb component

Currently, the PlayerCard component is managing its own states (its values change base on mutable states). We want it to have the same functionality as the MiniPlayerCard and practice keeping components dumb, so we want to let the ViewModel handle all of the logic.&#x20;

1. Take a look at the PlayerScreen.kt. This is where the PlayerCard is called. Notice that right now, the PlayerCard takes no arguments.&#x20;
2. To make it dynamic, we'll start by updating the arguments that PlayerCard needs to take: title, artist, isPlaying, progress, onPlayPauseClicked
   1. *Try to figure out the types on your own! title and artist are just Strings.*
3. Fix the error that occurs in the IconButton before moving on
   1. *Hint: use one of our newly defined arguments.*
4. &#x20;Update the PlayerCard call in PlayerScreen(). You'll need to get the data from the ViewModel. Here's an example of how to do that.
   1. *Hint: use MiniPlayerCard as a reference!*

```
val currentSong by viewModel.currentSong.collectAsState()
```

4. Once this is all set up, you should be able to run the emulator and see the PlayerCard changing progressing through and changing songs!


# A6: Rate My Vibe

Are you chill like dat?

This assignment has a release code! Please download it below, extract it, and open it in Android Studio. Ensure it can compile and run fine on your emulator before starting this assignment.

{% file src="/files/FyMUOGpkPcVoUS0KCTGW" %}
A5 Release Code!
{% endfile %}

For our fourth assignment, we'll have you implement lots of the UI/VM logic underlying a relatively complex, totally-AI-powered rating system for your photos! As many app ideas out there start using permissions and libraries to access features on your phone such as photos and the camera, this app will feature a **photo picker!**

The overall idea is that users will be able to upload their photos into this app's gallery, and get a vibe rating for each photo they upload.

Let's walk through the code, shall we?

***

## Release Code:

Expand each of the files below for a description of the release code!

<details>

<summary>PhotoRatingRepository.kt</summary>

This Model class provides mock networking/local storage functions that allow you to get photos by their stored ID, rate their vibes on 5 different scales, and save new images.

Here's a breakdown of the functions you'll use:

* `getImageFromId()`: Given an \[id], returns the image associated with it.
* `getImageIds()`: Returns a list of all image IDs.
* `happinessRating(), virtueRating(), anxietyRating(), excitementRating(), sadnessRating()` : Computes and returns a Float rating for the image associated with the input id. This is actually a pretty expensive computation, so it may take a while.
* `saveImage()`: Saves a new image to "local storage."

</details>

<details>

<summary>UIEvent.kt</summary>

This is the UIEvent class discussed in lecture.&#x20;

</details>

<details>

<summary>ImageCard.kt</summary>

A general-use component that displays an image that is either loading or loaded.

If the image input is null, the image is considered still loading.

If the image input is a proper bitmap (image array data), the image renders.

</details>

<details>

<summary>UploadImageCard.kt</summary>

A similar card to the above that instead displays a little indicator the user can press to, allegedly, upload a new image.

</details>

<details>

<summary>RatingLabel.kt</summary>

A bit of animated text with a label and a (loading) score that displays the rating/score of a particular emotion:

![](https://195521982-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LvOeSx5ZqjJA8sxykMu%2Fuploads%2FaBx3Gs702UZxi753BfDn%2Fimage.png?alt=media\&token=2b40cbf3-c7cc-4137-9421-48d75a8b9377)

</details>

There are some other files, but those should be self-explanatory or redundant--the important ones will be referenced in the **Requirements**.

***

## Requirements:

This assignment is also roughly split into "features."

(expand each of your features' requirements below!)

<details>

<summary>1) Rendering Photos</summary>

While we've provided a good amount of VM-M starter code, the images we've loaded in the home screen still aren't rendering.

1. Connect the images to the UI so that the UI renders an ImageCard for each image in the UiState.
2. Make sure the modifier here renders the image reasonably, still with `220.dp` height.

You should see the following (we added one test image automatically):

![](https://195521982-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LvOeSx5ZqjJA8sxykMu%2Fuploads%2FisBMxXMs7rNtLMQ3wUBg%2Fimage.png?alt=media\&token=d78427c8-d77e-42f2-91a0-a7112c09bb36)

</details>

<details>

<summary>2) Uploading Photos</summary>

Currently, nothing happens if you press on the Upload Image card! Let's implement the ability for the user to upload a new image, and have that image show up!

1. Add a new HomeViewModel function that fires when the user clicks the upload card.
2. Implement the above function such that a UIEvent is sent to the home screen.
3. Consume the event in the home screen, launching the photo picker. You can use the following snippet:

```kotlin
pickMedia.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly))
```

4. When the user successfully picks a photo, send that bitmap back to the VM! We've implemented the code that will automatically make this image show up if you've implemented (1-4) correctly.

See the completed demo for how images should pop up! (Don't worry about the fact that all images reload. That's our fault.)

</details>

<details>

<summary>3) Navigation</summary>

Currently, clicking on an ImageCard does nothing. Implement our card so we end up with navigation to the rating screen!

1. Add an onClick to ImageCard that is tied to the surrounding `Surface`.
2. When an ImageCard is clicked, navigate with the given `navController` to the rating screen, passing in the image ID as argument.&#x20;
   1. (NOTE: you can use `uiState.ids` to help with this purpose)
3. Tweak the existing `onLoadImage()` such that after the user uploads an image, they will also undergo this navigation 1s later, after `refreshImages()` is called.
   1. HINT: you will need to use a UIEvent; navController exists only in the UI. You will also need to call `delay(1000)`.

Again, see the demo to see what to expect!

</details>

<details>

<summary>4) Rating Nav Args</summary>

Let's switch gears over to the rating screen!

```kotlin
val id: String = checkNotNull(savedStateHandle["id"])
```

Note this line in RatingViewModel. This pulls the image ID nav arg given to the rating screen. Don't worry too much about how this works; just know `savedStateHandle` is used to pull navigation arguments via some Hilt/Lifecycle shenanigans.

1. Write an `init` block (this fires when the screen first opens) that loads the image and sends it down as a bitmap to the screen. You may reference examples in `HomeViewModel` for how to do this, especially with coroutines (like `viewModelScope.launch`).
2. Adjust `RatingScreen.kt` such that this new bitmap info is correctly sent to its `ImageCard`.
3. Make it so that the button "See my Rating!" is only enabled if the image bitmap is non-null.

You should see the image pop up after you are done! Note that you'll have to write your own UIState class and a few other things; we've intentionally kept this view model very bare bones as starter code.

Your image should now render (after it has loaded):

![](https://195521982-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LvOeSx5ZqjJA8sxykMu%2Fuploads%2FJw2fNgXfyMpt1Cj6V3gc%2Fimage.png?alt=media\&token=89a00eb2-c0df-49a9-97e8-3902112262dd)

</details>

<details>

<summary>5) Show the Ratings!</summary>

Finally, let's make it so that the user can indeed see their ratings! This implementation is a bit more open-ended, but please reference the demo video to see what we generally expect! Don't worry about making the UI exactly match.

1. After the user presses "See my Rating!", replace the footer to instead animate out and show 5 rating labels.
   1. Please use the colors we've given you: `Happiness`, `Excitement`, etc.!
2. As part of the UiState, load the ratings using `photoRatingRepository` and send them down through the screen, through the footer, to these labels.

</details>

If you've solved A5 correctly, your solution should look something like this...

{% file src="/files/lXDzvN1pQhTWCmmNYe0j" %}
A5 Solution Demo!
{% endfile %}

When you're done, export your project through Android Studio and upload your ZIP to CMS! Congrats on finishing A5!

<figure><img src="https://195521982-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LvOeSx5ZqjJA8sxykMu%2Fuploads%2FZCdAyNpc9uGbXMmoZwzS%2Fimage.png?alt=media&amp;token=5db0f40e-28aa-44fb-a525-71df566d298e" alt=""><figcaption><p>Exporting with File -> Export -> Export to Zip File...</p></figcaption></figure>

e


# Bonus Week | Hack Challenge & ML Kit

There's no starter code anymore...

For this semester's bonus week, we will be helping you get started with Android development for the Hack Challenge! Up until now, we've been providing starter code that contained the dependencies needed to implement navigation, MVVM, etc. If you feel lost on how to lay a strong foundation for your app, we hope this bonus lecture will help!

In addition to that, we will also have a bonus lecture on Google's ML Kit. Get ready to dive deep into how we can use machine learning as mobile developers!


# Relevant Links

**Monday Bonus Lecture Slides:** [\[Slides Link\]](https://docs.google.com/presentation/d/1zgMwCsJ7t88vBd-n6hEuV8sNJh9-RQoy9GSCMJK9P60/edit?slide=id.g385da76c1c9_0_1020#slide=id.g385da76c1c9_0_1020)

**Wednesday Bonus Lecture Slides:** \[TBD]

**Video Recordings:** \[TBD]


# 1. Introduction to the Editor and Views

Android Studio is the official Integrated Development Environment (IDE) for Android app development, based on [IntelliJ IDEA](https://www.jetbrains.com/idea/). Think of it like Eclipse or PyCharm, but that Android Studio is geared specifically towards creating and publishing Android applications. It comes with many features that help in development, most helpful of which is the ability to simulate programs on Android devices.

Android Studio also supports programming in two different languages:&#x20;

* **Kotlin** - the current industry standard
* **Java** - everyone's favorite legacy language

## Topics

* Introduction to the Editor
* SDK Management
* Kotlin Review
* Views


# 1.1 Introduction to the Editor

## Starting an Android Studio Project

&#x20;After you have successfully installed the Android Studio IDE, double-click the Android Studio application icon to start it. Click **Start a new Android Studio project** → then select “Empty Activity”. Name the project the same name that you want to use for the app.

![Create a new project window](https://lh6.googleusercontent.com/xidgJJYOgdAd4i2PAPuhvPhJPhYnPLm2oI-qnE4H2YHHlVS9KPvCI0Pcdx9cK6ctpxC07lhq8S3VBLiy2NbFqA9_R5GuUa-Neq7XNgLEsRr4ftxpgwMDppyCDyd5bGbpAY1t_vr3)

{% hint style="info" %}
**Note**: The company domain field must be unique if you wish to publish to Google Play, as that's the identifying name that differentiates your app from anyone else’s.
{% endhint %}

## Editor Layout

The standard layout of the Android Studio editor has several logical panes that in all have all the functions you'll need to develop for Android.&#x20;

![Breakdown of Android Studio editor](https://195521982-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LvOeSx5ZqjJA8sxykMu%2F-LvZI1SrndGON-kEQjOH%2F-LvZNhObKu-xFRemrBbb%2Fimage.png?alt=media\&token=4c12986b-347d-47a3-9e74-f2bdcbfe3749)

The main five components of the editor that we'll touch upon includes toolbar, project pane, editor, menu bar, and bottom menu bar.&#x20;

### Toolbar

This is the bar near the top containing shortcuts to different actions within Android Studio. The most important is the green triangle allowing you to run your program. If you press it, you should see a simulator pop up and run the code.

### Project Pane

Just as in most IDEs, our project files are available in the left third of the screen. You can navigate to different files in your project by using the project pane. One neat trick is to go to the toolbar at the top of the project pane and toggle from **Project** to **Android**; this way, you can navigate directly to the java and XML files.&#x20;

### Editor

This is the component of the screen where the code is written. If you just created a new activity, you should see something like this:&#x20;

```kotlin
class MainActivity : AppCompatActivity() {
  override fun onCreate(savedInstanceState: Bundle?) {
    ...
  }
}
```

The code above might be a bit difficult to understand. For now, think of `onCreate()` method of `MainActivity` as the first method that is executed when running our application, similar to how `public static void main(String[] args)` is first executed in Java.

### Bottom Menu Bar

The bottom menu bar returns output from the compiler and displays useful information about gradle and compile time errors. The bar also houses the debugger, which is a great tool to use for understanding errors.&#x20;

## Android Emulator

Android emulators are what developers use to test their application. A selection of available emulators shows up in the Toolbar, directly to the left of the green play button. Your [connected](https://developer.android.com/studio/run/device) Android devices will also show up here, which can also be used to test your application. To create a new Android Emulator, you must use **Tools > AVD Manager** to create and edit Android Virtual Devices.&#x20;

Emulators are valuable tools for testing, but beware of the drawbacks. They will most likely be slower than their physical counterparts and will most definitely slow down your machine while you're running them. They often don't have as many capabilities as the real phone and certain functionality (like getting user location) is often absent. For applications that require user data, such as location or storage, it is recommended to test directly on a physical device.&#x20;

## Logcat

Android Studio doesn’t have a traditional version of the console that IDEs like Eclipse and PyCharm do. Instead, Android Studio uses what is referred to as Logcat. To open Logcat, navigate to **View > Tool Windows > Logcat.**&#x20;

Our end users will never be interacting with Logcat, so this feature is only used by the developer for debugging purposes. Therefore, unlike Java’s standard `System.out` methods for printing to the console, Android Studio has it’s own conventions:

* `Log.d(tag, message)` for logging debugging statements&#x20;
* `Log.i(tag, message)` for printing useful information to developers&#x20;
* `Log.w(tag, message)` for outputting a warning (generally for operations that should never occur)
* `Log.e(tag, message)` for outputting an error message

Here's an example of how you might using the Logcat for debugging. In this example, we're printing out the string `name` to the console:&#x20;

```kotlin
var name = "Android"
Log.d("tag_string", name)
```

Now, let’s look at the Logcat. If we re-run our project, we’ll see a huge stream of data that is completely independent of what we’ve told our program to print. You can filter out the stream by typing "tag\_string" into the search bar and you should see the line:&#x20;

```
<DATE> <TIME> <PID>-<TID>/com.example.myapplication I/tag_string:Android
```

&#x20;


# 1.2 SDK Management

## What is an SDK?

Each Android device has installed on it a version of the Android Operating System (OS); The latest version is Android 10, which came out in August of 2019. Each version of the Android OS corresponds to a unique Software Development Kit (or SDK) -- Android 10 is running SDK 29. An SDK can be thought of as all the tools that can be used by the developer when developing an application that will run on that device.

The minimum SDK sets the lowest level possible that a system must have to run your app. For example, if the SDK of an Android project was set to 22, then any phones with SDK 22 or higher could run that project. This minimum SDK level for is set with the `minSdkVersion` field in your application’s `app/build.gradle` file. Don’t worry about manipulating this for now -- just know this file is used for managing all the dependencies that are necessary for your application to run.

## Tradeoffs with SDK Versions

Google is constantly releasing interesting new features in their latest SDKs, but a developer should be cautious of immediately implementing these features. Using something in the codebase that is reliant on having a high SDK like 28 or 29 can lead to some drastic consequences.

Only around 10% of Android users are running SDK 28 or above, while over 80% have an SDK ≥ 21. So if your app was reliant on a brand new feature that Google developers just released in August (i.e. special geolocation calculations), you would need to raise your `minSdkVersion` to 29. This means that the app will crash on any devices with a SDK level under 29.&#x20;

Raising the minimum supported SDK version prevents older devices from downloading your application and limits the target audience

Here is a table of the percentage of Android users who own a device that supports a specific SDK version.&#x20;

![](https://195521982-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LvOeSx5ZqjJA8sxykMu%2F-MCFNAOxUXyvfYW-IyxC%2F-MCFOQBw9JBaKVvNDdB-%2FAnnotation%202020-07-14%20225232.jpg?alt=media\&token=21e4bb92-52af-420e-b74a-66575d101579)

In general, we recommend setting the minimum SDK supported by your application to be between 21 and 24, and raising the SDK level only when necessary.&#x20;

## SDK Manager

The Android SDK Manager handles downloading and updating the various libraries required for the different versions of Android. Here, you can download different SDKs which can then be used to create a virtual device with a specific SDK/API (to simulate testing on users with older/newer devices)

Navigate to **SDK Tools** in the SDK manager. Several things should already be installed:

* **Android SDK Build** **Tools:** required to build Android apps
* **Android SDK Platform** **Tools:** required to communicate with Android devices through the Android Debug Bridge (also known as ADB)
* **Android SDK Tools:** required to simulate a specific Android SDK
* **Android Emulator:** required to run any emulator

Generally, you would be needing to visit the SDK Manager unless the SDK version of your application changes.&#x20;


# 1.3 Kotlin Overview

If you've never programmed in Kotlin before, you might feel intimidated. Don't worry, it's a fairly intuitive language that simplifies the coding experience while providing powerful tools that ease the development of complex tasks. We'll provide a quick rundown of how it operates, but if you'd like something more in-depth, the Kotlin team has built a great tutorial at <https://play.kotlinlang.org/koans/overview>.

## Variables

Kotlin is a statically-typed language, meaning that the type of the variable is set when being initialized. However, unlike the versions of Java you were most likely taught, Kotlin prefers to infer the type for you. For example, when initializing a variable named `x` to the integer value of 3, you can simply declare `var x = 3`.

Kotlin has now inferred that variable to be of type `int`, and as long as this variable is in the scope of the program, the type of x cannot be changed. Setting `x = "hello"` will lead to a compiler error. However, there are cases when Kotlin does not have enough information to infer the variable type. You can explicitly set the type with `var x : String = "hello"`

Java may have gotten you into the habit of **primitives** and **objects**. You can ignore such details when using Kotlin. It doesn't have primitives on the user side, only objects.

### Mutability

There are two ways that you can declare a variable: `val` and `var`. `val` is used when the variable should not change through the duration of your code, and **by default**, you should try to use `val` whenever you can. If you do try to change the immutable variable, the compile will throw an error and you'll know at compile time. `var` is used for variables that are allowed to be mutable.&#x20;

### Nullability

What makes Kotlin easier to work with compared to Java is that null checking happens during compile time instead of runtime. By default, variables in Kotlin are not nullable, and setting a variable to null will yield an error. To make a variable nullable, you'll need to add a `?` to the variable declaration like so `var x?` or `var x: String?`

## Objects

```kotlin
class Cat(val name: String, var age: Int) {
    constructor(name: String) : this(name, 0)
    constructor(yearOfBirth: Int, name: String) : this(name, 2022 - yearOfBirth)
}

var a = Cat("Okayu", 18)
```

If that last example didn’t make any sense, don't worry -- we've got a quick overview of Kotlin's object system. An object type is always defined by a class and allows us to create instances of that class. For example the class `Cat` we defined above allowed us to create the `Cat` object a. The standard class file has three main things:

### **Instance variables**

These are properties that are unique to each instance of an object. The instance variable type is shared among all objects but the value is different. For example, every cat has an age and a name, yet the values are different from cat to cat.

### **Constructor**

The constructor is a special method that allows a programmer to create new instances of that class. Constructors typically update the instance variables of an object with specific values that are passed in, and are called as in the above example with:&#x20;

```kotlin
<ObjectType> o = <ObjectType>(<Constructor arguments>)
```

If no constructor is defined for a class, Kotlin will create a default constructor that can take all the arguments detailed in the definition.

### **Methods**&#x20;

These are easily-repeatable chunks of code that typically perform some action using an object’s instance variables. Methods take arguments as inputs and can return a value as an output. Because Kotlin is statically-typed, we must define the types of the arguments and the return type when we create our method. Two methods are given for the `Cat` class below:

```kotlin
class Cat(val name: String, var age: Int) {
  val isNewborn
    get() = age == 0
    
  fun getBirthYear() : Int {
    return 2022 - age // close enough
  }
}
```

The first method is an getter created for the value `isNewborn`, which will return a true or false depending on the age. The second method roughly returns the birth year of the cat. Now, we can execute these methods on our cat a defined above.

```kotlin
if (!a.isNewborn) {
    val birthYear = a.getBirthYear() // birthYear = 2000
}
```

### Getters and Setters

Kotlin automatically creates getter and setter methods for instance variables inside of data classes. Look at the code below to see how to use these methods:

```kotlin
data class Cat (
    val name: String,
    val birthYear: Int
)

val newCat = Cat("Felix", 2000)

// getter
val x = newCat.name // x = "Felix"

// setter
newCat.name = "Tammy" 
val y = newCat.name // y = "Tammy"
```

You can also override these methods:

```kotlin
var isNewBorn: Boolean
    get() = age == 0
    set(value) { value < 2022 }
```

Finally, you can also make getter and setter methods to private so that the method can only be used inside of its own class. This is a good way to protect the value of variables from being accidentally changed.

```kotlin
var isNewBorn: Boolean
    private set
```

## Modifiers

Modifiers place restrictions on variables, methods, and classes and exist before the static type, return type, or word ‘class’, respectively. Some common modifiers include:

### **`public`/`private`/`protected`/`internal` modifiers**

These define the accessibility of a variable, method, or class in Kotlin. Something declared public can be accessed from anywhere within your project, something private can only be accessed from within the same class, something internal is within the same class and all it's subclasses, and something protected (i.e. method, class, variable) can be accessed from any file within the same package.&#x20;

## Inheritance

Kotlin is amazing because basically every class represents its own unique Object type -- forcing programmers into a coding style of separating functionality into different files. Kotlin also has a powerful system of inheritance, allowing programmers to reuse code in the most efficient way possible. This becomes especially prevalent in Android development, as many objects in the Android class system share a lot of functionality.

### Subclasses

This technique allows us to further break down our classes into increasingly more specific types. For example, we could create a class representing a `Sphynx`, and give it access to everything in `Cat` without explicitly copying the code. By default, classes in Kotlin cannot be inherited from, to do you you must explicitly state that said class is `open`.

```kotlin
open class Cat(val name: String, var age: Int) {
    ...
}

class Sphynx(name: String, age: Int) : Cat(name, age)
```

Now we could create a `Sphynx` object, and use all the methods and access the same instance variables from before. The super method call in the `Sphynx` constructor above calls the `Cat` constructor with arguments name and age, thereby setting the instance variables.

```kotlin
val b = Sphynx("Riza", 3022)
val birthYear = b.getBirthYear() // b = -1000
```

We can also create b by using `Cat` as the static type of `b` because this Sphynx is still a `Cat` object (we told Java this with the extends keyword).

### Method Overriding

Now, we can override certain methods of our parent `Cat` class in `Sphynx`. Overridden methods must have the **exact same** method signature as the parent method, with the same method name, return type, and argument types. Here is the overridden `getName()` method:

```kotlin
class Sphynx(name: String, age: Int) : Cat(name, age) {
  override fun getBirthYear() : Int {
    throw Exception("(^_^)")
  }
}
```

Now, calling `b.getBirthYear()` will throw a funny error.

## Interfaces

Interfaces are special classes used for organizational purpose that cannot be instantiated. A class implements an interface, which forces the programmer to provide a method body for all the methods defined in the interface.&#x20;

There are a few other Kotlin things you ought to be familiar with (initialization of arrays, conditionals, arithmetic operations, etc.), so we recommend you take a look at [Kotlin Koans](https://play.kotlinlang.org/koans/overview) by the Kotlin team themselves! Otherwise, a solid foundation of Java will get you most of the way through understanding the concepts in Kotlin. With that being said, as with most things, practice makes perfect.


# 1.4 Views

Wait a second, it seems like XML files give us everything we need to display an application on a screen! Did we really need that code to change the `TextView` text to "Hello Friend"? Well, declaring the attributes inside the XML for a static `TextView` seems fine, but when we start to look at more complex View elements, we’ll need corresponding logic to control how these components work.

![Main Activity in Eatery](https://195521982-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LvOeSx5ZqjJA8sxykMu%2F-LvZH3TGSzyS8bKiA1lT%2F-LvZTn5RLz_enwZdIZHv%2FScreen%20Shot%202019-12-08%20at%202.47.59%20AM.png?alt=media\&token=bdfb2385-fed4-4f0f-8282-d7bd6cd43d7b)

Important views, such as the ones seen above, that you’ll see used often include:

* (1) `EditText` for inputting and editing text
* (2) `Button` and other clickable elements for supporting interactive behavior
* (3) `TextView` for displaying text
* (4) `ImageView` for displaying images

In sections 2.4 and 2.5, we will go in depth into two particular views that handle user interactions: input controls and buttons.&#x20;


# 1.5 Android Studio Project Demo + Understanding The Editor

## Setting up a new Android Studio project

1. Click **File > New Project > Empty Activity > Next.** For most of your projects, you'll want to start with an empty canvas. There are also many pre-made templates for different type of screens, which may be helpful to check out if you're working on a quick prototype. In general, we pick an empty activity to avoid having to deal with boilerplate code.&#x20;

![Project Template dialog window](https://195521982-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LvOeSx5ZqjJA8sxykMu%2F-MHTRFtxgIGQ6gu02rMI%2F-MHTTHS358RIcbQC1VwO%2Fimage.png?alt=media\&token=f916e720-834d-49b2-b85f-1386c66d1df0)

2\. Fill in the input fields for project name and save file location (on computer). It is also important to verify that the language is set properly to Kotlin or Java at this step; however, it'll require more work later to switch the primary language for this codebase.&#x20;

![Project Configuration page](https://195521982-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LvOeSx5ZqjJA8sxykMu%2F-MHTRFtxgIGQ6gu02rMI%2F-MHTWTwMYucOlrnmpL-g%2Fimage.png?alt=media\&token=a1f104eb-7a9f-43b2-8423-de1f4cbf7c0e)

It is also important to find a minimum SDK that is low enough to encompass most app users, but high enough that the application can support cool and new features. We recommend using **minimum SDK 23**, and you can always increase the minimum SDK later in the codebase as needed.&#x20;

## Understanding files in Android Studio&#x20;

Android Studio will repopulate the editor with pre-made files so that the app can compile and run even without you needing to write any code!

![File Structure in Andorid S](https://195521982-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LvOeSx5ZqjJA8sxykMu%2F-MHTRFtxgIGQ6gu02rMI%2F-MHTZZ__S7grO-RZl8F_%2Fimage.png?alt=media\&token=6def4128-5e40-404d-a8bf-23ea09350222)

The different files include:&#x20;

* `MainActivity`: This is your Kotlin/Java file that contains all the logic to your app. If you open the file, you'll notice that all it does by default is populate the view on an `onCreate()` event. (This is called when the app first launches).

{% hint style="info" %}
You can think of an activity as a screen inside your application, for now. We will talk about more complex screens in Lecture 6 and beyond.&#x20;
{% endhint %}

* `res`: This folder contains all the resources needed in your app:

  * drawable: contains app icons and images
  * layout: XML layouts used in the app
  * values: values for color, strings, styles, etc.

* `AndroidManifest.xml`: This file contains information about the activities in your project, granted permissions and intents, and other system-level information. For the most part, you shouldn't need to touch this file unless you are adding new permissions.&#x20;

* `build.gradle`(module): This file is where you set the basic configuration for the app, which is useful when you upload your project to the Google Play Store. You can also add plugin and import third-party libraries here.&#x20;

These files and folders above are the most important ones that you will probably be editing as you develop an app. If you find yourself editing other files, double check that you can editing the right files!

Check out this page if you want to learn more about the difference between a layout and a class file:

{% content-ref url="/pages/-LvOfDFpYd8FIuJeQcLA" %}
[1.1 File Structure and File Types](/archive/archived-native-android-textbook-pages/2.-views-and-layouts/2.1-file-structure)
{% endcontent-ref %}

## Exploring the editor

This page has a good overview of the different components of the editor:&#x20;

{% content-ref url="/pages/-LvOfISWOCT5xU7hzvCq" %}
[1.1 Introduction to the Editor](/chapters/1.-introduction-to-the-editor/1.1-intro-to-editor)
{% endcontent-ref %}

Some additional components of the IDE that you may use include:

* Logcat: This window allows you to see and filter through system debugging messages, which is super useful for debugging code.&#x20;

![Logcat window](https://195521982-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LvOeSx5ZqjJA8sxykMu%2F-MHTRFtxgIGQ6gu02rMI%2F-MHTeLrja-x_6dWyr_tA%2Fimage.png?alt=media\&token=6fd8e900-3d29-4904-879e-5f91e8a895d7)

* SDK Manager: This window allows you to view and download new SDK packages that you want your application to support. This is really useful for when you want to work with new features on Android and need a higher SDK level.&#x20;

![SDK Manager](https://195521982-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LvOeSx5ZqjJA8sxykMu%2F-MHasp9gtrkTWVycKbSS%2F-MHatuOCLSfOmX1WIYoN%2Fimage.png?alt=media\&token=7af9a42b-7abc-4c04-a08c-0b0d5a37d1a6)

* Layout Inspector: This shows you the hierarchy of all the visual components in your app as well as the constraints and values used, and this is a great debugging tool for when you are creating UI dynamically.&#x20;

![Layout Inspector](https://195521982-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LvOeSx5ZqjJA8sxykMu%2F-MHasp9gtrkTWVycKbSS%2F-MHaumXdea8-vOvzMVoe%2Fimage.png?alt=media\&token=5a9f532b-4223-46b5-b0d3-a355bfd0729e)

## Creating and running an emulator

1. &#x20;Open the AVD Manager from the top toolbar.&#x20;

![Device Dropdown](https://195521982-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LvOeSx5ZqjJA8sxykMu%2F-MHautCkVSbYKiGpMFXd%2F-MHavZVmAsnm_nuupzII%2Fimage.png?alt=media\&token=f41c1ddf-e071-4785-84fa-aa21d4769a07)

2\. Click **Create Virtual Device** and specify the device specs and system image that you want your emulator to have.&#x20;

![Virtual Device Configuration Window](https://195521982-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LvOeSx5ZqjJA8sxykMu%2F-MHautCkVSbYKiGpMFXd%2F-MHawjN8nF8sqnBs9UL0%2Fimage.png?alt=media\&token=b858a3c4-cca6-464f-bfb6-e87ff9c944df)

3\. Once you finish building your device, you'll see it in the list of created virtual devices. When you click run, the device will be created with your app already installed. Your device should look something like this:&#x20;

![Nexus Emulator ](https://195521982-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LvOeSx5ZqjJA8sxykMu%2F-MHautCkVSbYKiGpMFXd%2F-MHawwjlwA1f6kcj3ULr%2Fimage.png?alt=media\&token=4ffbb2d6-44de-4784-ac94-a2257def406b)

## Demos

{% file src="/files/n5zKlUUbktCFPBqbbfwK" %}
Demo Used in Lecture 1
{% endfile %}

{% file src="/files/9HScHcRbE8c5Jw453pCR" %}
Component Customization Exercise Solution for Discussion 1
{% endfile %}


# 2. Jetpack Compose

[Jetpack Compose](https://developer.android.com/jetpack/compose) is a modern toolkit designed to simplify UI development. It combines a reactive programming model with the conciseness and ease of use of the Kotlin programming language. It is fully declarative, meaning you describe your UI by calling a series of functions that transform data into a UI hierarchy. When the underlying data changes, the framework automatically re-executes these functions, updating the UI hierarchy for you.


# 2.1 Introduction

Providing an overview of Jetpack Compose

Unlike Native Android, which is covered in the chapters up until this point, Jetpack Compose uses purely Kotlin without XML layout files. You may be wondering how Jetpack Compose achieves this while knowing where to arrange the components. The secret lies in the building blocks of Jetpack Compose: Composable Functions. Instead of using activities and fragments as we would in Native Android, instead everything we want the users to see is annotated with an @Composable tag. This allows users to define their UI programmatically rather than worrying about the semantics of UI construction.


# 2.2 Layouts

Organization slay

As you may recall from Native Android, UI elements live in a hierarchy, where each element has a parent element by which it is contained. Jetpack Compose allows us to create this hierarchy by calling other composable functions inside the one we are creating. However, if you try just putting two Text composables right after one another, you will notice that they will overlap and make the text unreadable. Therefore, we need some ways to organize the composables to our liking.

Jetpack Compose provides multiple methods to arrange multiple composable functions depending on the format the user wants. The most common ones are **Rows**, **Columns**, and **Boxes**. As the names suggest, Rows allow us to put composables in a row with one another, while Columns allow us to put composables in a column with one another. Boxes may seem a bit more unintuitive, but they allow us to arrange the composables inside of it however we like, including overlapping composables, which Rows and Columns do not allow.

<figure><img src="https://195521982-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LvOeSx5ZqjJA8sxykMu%2Fuploads%2Fdkn7VirCA3Ib2BXtbl0i%2Flayouts.svg?alt=media&amp;token=293d2c51-3750-4887-8978-bf8e73f2326c" alt=""><figcaption><p>A visual representation of layouts provided by the official Android Developers website</p></figcaption></figure>

Another thing to note is that while in Native Android, we want to avoid nested layouts because it slows down runtime, Jetpack Compose has optimized for layouts and therefore the performance will not be impeded by nested layouts.

### Alignment and Arrangement

When we want to manage the layout of items within a Row or Column, there are some arguments that we can specify in order to do so. For example, as you can see below, we can use the **verticalAlignment** and **horizontalArrangement** arguments in order to specify that we want the composables inside the Row to be centered vertically and right-aligned (using Arrangement.End). Similarly, Columns have **horizontalAlignment** and **verticalArrangement** arguments to specify how the composables inside should be arranged.&#x20;

```
@Composable
fun ArtistCard(artist: Artist) {
    Row(
        verticalAlignment = Alignment.CenterVertically,
        horizontalArrangement = Arrangement.End
    ) {
        Image(/*...*/)
        Text { /*...*/ }
    }
}
```

Here's a list of Arrangements and Alignments from a [website](https://vitor-ramos.medium.com/understand-arrangement-and-alignment-in-jetpack-compose-7633f2ed5b39) discussing the intricacies of Jetpack Compose Layouts that may come in handy:

* Arrangement.Start: places items in the main axis start.
* Arrangement.End: places items in the main axis end.
* Arrangement.Center: places items in the main axis center.
* Arrangement.SpaceBetween: places first item in the main axis start and the last item in the main axis end, then distributes the remaining items evenly in the space left.
* Arrangement.SpaceAround: puts the same amount of space on the left and the right of each item (in a *Row*, or top and bottom in a *Column*). Notice that spaces in the middle will be twice as large as spaces in the ends.
* Arrangement.SpaceEvenly: puts the same amount of space between items, the start and the end. It differentiates from space around because the spacing at the start and end is the same as the spacing in the middle.

When using a **Box**, the composables inside of it will have padding from the boundaries of it. Therefore, we can allow multiple composables to overlap simply by adjusting their paddings to make them in similar places. Here's an example from the Scooped profile screen:

```
Box(
    modifier = Modifier.fillMaxSize(),
) {
    Image(/*...*/)
    Column {
        Spacer(
            modifier = Modifier
                .height(133.dp)
                .fillMaxWidth()
        )
        /*...*/
    }
    Image(
        painter = /*...*/,
        contentDescription = null,
        modifier = Modifier
            .padding(top = 68.dp)
            .size(128.dp)
            .clip(CircleShape)
            .align(Alignment.TopCenter)
            .border(width = 3.dp, color = Green, shape = CircleShape)
    )
}
```

Also, you may notice that we have a modifier inside the Image referencing **Alignment.TopCenter**. The reason that we're able to do this is that we are able to use either the **contentAlignment** argument for the Box itself or we can use the modifier.align() method on the children of the Box.


# 2.3 Modifiers

Modifiers allow you to decorate or augment a composable. Simply having composables is not enough for front-end development. You also need to customize the behavior and appearance of composable functions.&#x20;

Modifiers are mainly used for layout modification, appearance customization, and event handling. We will first look at an example of how to use modifiers, and then dive into the specific usages.

## Example Usage

```kotlin
@Composable
private fun greeting(name: String){
    Column(
        modifier = Modifier
            .padding(8.dp)
    ){
        Text(text="Hello,")
        Text(text=name)
    }
}
```

Modifiers are applied to a composable by using the `modifier` parameter. Here we are adding padding to the Column composable.

## Chaining modifiers

Modifiers can be chained together to apply multiple effects.&#x20;

```kotlin
@Composable
private fun greeting(name: String){
    Column(
        modifier = Modifier
            .padding(8.dp)
            .fillMaxWidth()
            .background(Color.Red)
    ){
        Text(text="Hello,")
        Text(text=name)
    }
```

Here, we have the same composables but we are applying three modifiers to the Column, instead of one.

The order of modifiers can affect the result, so it's important to chain them thoughtfully. Folloiwng is an example of how the same modifier functions can give different results if the order is changed

```kotlin
Text(
    text = "Background first, then padding",
    modifier = Modifier
        .background(Color.Green)
        .padding(16.dp)
)

Text(
    text = "Padding first, then background",
    modifier = Modifier
        .padding(16.dp)
        .background(Color.Green)
)
```

<figure><img src="https://lh7-rt.googleusercontent.com/slidesz/AGV_vUcCr9oepamkcoAUTCAVyVuE0yfFZH2Q7VGgecvZF3WeDbaISBWo3Anekd_0J7SCKnF4rnWzsaPshNsRpyZhDpS9lBNNXnS7dVAQTLp0IFIVnahV6xkdz-HEnriXsz6dsjPpjwrSZwcuGfBactBEfztXPhizeLxO=s2048?key=QyubbP5xsj-qKk77cGF9sA" alt=""><figcaption></figcaption></figure>

## Some Common Modifiers

**Layout Modification**

* `Modifier.padding()` - Adds padding around a composable.
* `Modifier.size()`, `Modifier.width()`, `Modifier.height()` - Sets the size of a composable.
* `Modifier.fillMaxSize()`, `Modifier.fillMaxWidth()`, `Modifier.fillMaxHeight()` - Makes a composable fill available space.
* `Modifier.align()` - Aligns a composable within a parent layout.

**Appearance Customization**

* `Modifier.background()` - Sets the background color or drawable.
* `Modifier.border()` - Adds a border with specified color, width, and shape.
* `Modifier.clip()` - Clips the composable to a specified shape.

**Behavior Modification**

* `Modifier.clickable()` - Makes a composable respond to click events.
* `Modifier.draggable()`, `Modifier.scrollable()` - Adds drag or scroll behavior.


# 2.4 Animations

Animations are crucial in providing a smooth and engaging experience for the users. Jetpack Compose provides an extensive list of APIs that can be used to implement different animation in an app. Many Jetpack Compose animation APIs can be used as composable functions like other layout and ui elements.&#x20;

## AnimatedVisibility

The `AnimatedVisibility` composable animates the appearance and dissapearane of elements. By default, the enter transition is fading in and expanding, and the exit transition is fading out and shrinking. The transition can be customized by specifying **`EnterTransition`** and **`ExitTransition`**.&#x20;

**`EnterTransition`** examples: `fadeIn`, `slideIn`, `slideInHorizontally`, `slideInVertically`, `scaleIn`, `expandIn`, `expandHorizontally`, `expandVertically`

**`ExitTransition`** examples: `fadeOut`, `slideOut`, `slideOutHorizontally`, `slideOutVertically`, `scaleOut`, `expandOut`, `shrinkHorizontally`, `shrinkVertically`

Transitions can be combined using a **`+`** operator in between `EnterTransition` or `ExitTransition` objects.&#x20;

Content within `AnimatedVisibility`, direct or indirect children, can use the **`animateEnterExit`** modifier to specify a different animation behavior for each child. The visual effect for each child is a combonation of the animation specified in the `AnimatedVisibility` composable and the child's own animation.&#x20;

```kotlin
AnimatedVisibility(
    visible = visible,
    enter = fadeIn(),
    exit = fadeOut()
) {
    // Fade in/out the background and the foreground.
    Box(Modifier.fillMaxSize().background(Color.DarkGray)) {
        // Box is a child of AnimatedVisibility
        Box(
            Modifier
                .align(Alignment.Center)
                .animateEnterExit(
                    // Slide in/out the inner box.
                    enter = slideInVertically(),
                    exit = slideOutVertically()
                )
                .sizeIn(minWidth = 256.dp, minHeight = 64.dp)
                .background(Color.Red)
        ) {
            // Content...
        }
    }
}
```

If each child wants its own distinct animation, AnimatedVisibility can apply no animations  by specifying **`EnterTransition.None`** and **`ExitTransition.None`** in the AnimatedVisibility composable.&#x20;

## animate\*AsState

The `animate*AsState` functions are the easiest to animate a single value. The function takes in an end value, and the API starts the animation from the current value to the end value.&#x20;

```kotlin
val alpha: Float by animateFloatAsState(if (enabled) 1f else 0.5f)
Box(
    Modifier.fillMaxSize()
        //alpha is the opacity of the box
        .graphicsLayer(alpha = alpha)
        .background(Color.Red)
)
// changes the opacity of a red rectangle between 1f to 0.5f
```

Jetpack Compose provides `animate*AsState` functions for `Float`, `Color`, `DP`, `Size`, `Offset`, `Rect`, `Int`, `IntOffset`, and `IntSize`. Other data types can be supported by providing a `TwoWayConverter` to `animateValueAsState` that takes a generic type.&#x20;

## Customize Animations

Most animation APIs allow developers to cusomize the animation by an optional `AnimationSpec` parameter. Dependent on the animation type, the `AnimationSpec` will also be different.&#x20;


# 2.5 Lazy Lists

Lazy lists are used to display a large number of items in a list. Using a Column or Row can cause performance issues since all of the items will be composed whether they are visible or not. LazyList can be compared to a RecyclerView in Android Native, but it eliminates the need for an Adapter.

**LazyRow** and **LazyColumn** are components provided by Jetpack Compose that display only the visible items in a designated orientation. As the name suggests, LazyRow creates a horizontal scrolling list, and LazyColumn creates a vertically scrolling list.&#x20;

Unlike most layouts in Jetpack Compose, Lazy components do not accept a `@Composable` block as a parameters. Instead, Lazy components provide a **`LazyListScope()`** block, which offers a domain-specific language (**DSL**) that allows the app to describe the item contents. LazyRow and LazyColumn are then responsible for adding each item's contents to the list.

### LazyListScope DSL <a href="#lazylistscope" id="lazylistscope"></a>

LazyListScope provides many functions that describe the items in the layout.&#x20;

Most commonly, **`item()`** is used to add a single item, and **`items(int)`** are used to add a certain number of items.&#x20;

```kotlin
LazyColumn {
    // Adds a single item
    item {
        Text(text = "First item")
    }

    // Add 5 items
    items(5) { index ->
        Text(text = "Item: $index")
    }

    // Adds another single item
    item {
        Text(text = "Last item")
    }
}
```

There are also extension functions like `List` that can add a collection of items and `itemsIndexed()` where the content of an item is aware of its index. These functions are not as commonly seen, so they won't be covered in depth.&#x20;

## Padding

To make the list more visually appealing, padding can be added to the edges of the content. Lazy lists allows you to pass in **`PaddingValues`** to the **`contentPadding`** parameter.&#x20;

```kotlin
//LazyColumn with horizontal padding of 16.dp and vertical padding of 8.dp
LazyColumn(
    contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp),
) {
    // ...
}
```

## Spacing

Spacing can also be added in between items in the lazy component by using **`Arrangement.spacedBy()`**. LazyColumn sets the parameter **`verticalArrangement`** while LazyRow sets the parameter **`horizontalArrangement`**.&#x20;

```kotlin
//LazyColumn with 4.dp of space between each item
LazyColumn(
    verticalArrangement = Arrangement.spacedBy(4.dp),
) {
    // ...
}

//LazyRow with 4.dp of space between each item
LazyRow(
    horizontalArrangement = Arrangement.spacedBy(4.dp),
) {
    // ...
}
```

## Tips for Lazy Lists

* #### Avoid using 0-pixel sized items <a href="#avoid-0-size-items" id="avoid-0-size-items"></a>

  Even if you want to load in the data later, set default sizing so that the Lazy list can calculate the number of items it can fit in the viewport.
* **Avoid nesting components that scroll in the same direction**

  Nesting scrollable children without a predefined height inside a parent that scrolls in the same direction will throw an `IllegalStateException.` Instead, wrap everything into a Lazy list and use the DSL to pass in different types of content.
* **Avoid putting multiple elements in an `item()`**

  The elements will be unable to be composed seperately, which can lead to complexities if you only want one specific element to show. It can also interfere with other functions like `scrollToItem()`.


# 2.6 Reactive UI

Reactive UI falls under the category of **declarative programming**, allowing developers to focus on what the UI should accomplish rather than how to achieve it. Let's explore the key concepts behind Reactive UI and how it leverages **states** to simplify UI development.

### Imperative vs. Declarative Programming

Before diving into reactive principles, it’s important to understand the difference between **imperative** and **declarative** programming paradigms.

* **Imperative programming** describes the steps necessary to achieve a particular result. You explicitly instruct the program on how to perform tasks.

  For example:

```kotlin
var greeting = "Hello"
Text(text = greeting)
greeting = "Goodbye"
// The UI won't change unless you manually update it
Text(text = greeting)
```

* **Declarative programming** describes what the program should accomplish, but not the specific steps to achieve it. The framework takes care of rendering changes as necessary.

  In declarative programming, the UI reacts to changes in data automatically:

```kotlin
var greeting by remember { mutableStateOf("Hello") }
Text(text = greeting)
greeting = "Goodbye"
// The UI will update automatically
```

### What is Reactive UI?

Declarative programming is a broader concept that focuses on *what* the program should accomplish. **Reactive UI** falls under declarative programming, and it is about responding to changes in data over time.

A Reactive UI automatically reacts to changes in underlying data, reducing the need for manual UI updates. Instead of explicitly triggering updates for UI components when data changes, the system handles recomposition behind the scenes.

In Jetpack Compose, the UI reacts to changes in state variables, which means the UI components tied to these state variables will automatically recompose (redraw) when the state changes.

In the example for declarative programming, `greeting` is a **state variable**. When its value changes, any composable that reads this value automatically updates, reflecting the new data.

### States in Jetpack Compose

In Compose, states are used to manage the UI’s reactive data flow. Instead of creating plain variables, you define state variables using `mutableStateOf()`.

```kotlin
var name by remember { mutableStateOf("") }
```

Let's break it down:

* `by` allows `name` to be used as a regular variable instead of accessing the state explicitly.
* `remember` ensures the state is preserved across recompositions. When the composable function recomposes (redraws the UI), `remember` ensures that the state value isn't reset.
* `mutableStateOf("")` initializes the state with an empty string, which can be updated.

### Why Use States?

Using state variables provides a powerful way to handle dynamic UI changes. Here’s why:

* **Automatic UI updates**: Using regular variables can be problematic, as whenever a variable changes you have to call all the UI components that depend on it to change them. However, when a state variable changes, the UI components that depend on that state automatically recompose, so you don't need to manually update the UI.
* **Cleaner code**: Instead of explicitly calling functions to update the UI when a value changes, state variables allow for automatic synchronization between data and UI.

Here's another example of when states are useful:

```kotlin
var count by remember {mutableStateOf(0)}

//UI that reacts to the state
Column(
    modifier = Modifier.padding(16.dp)
){
    Text(text = "Count: $count"
    Spacer(modifier = Modifier.height(16.dp))
    Button(onClick = {count++}){
        Text("Increment")
    }
}
```

In imperative programming, you would have to manually update the UI each time the `count` increments, by calling methods to refresh or redraw components

With this reactive approach, whenever `count` changes, the UI automatically recomposes without any manual intervention.

Reactive UI in Jetpack Compose works hand-in-hand with reactive data flow. The idea is that you define how the UI looks based on the current state, and then pipeline the data in. The framework takes care of updating the UI whenever the state changes. This leads to more intuitive, easier-to-maintain code, especially as your apps grow in size and complexity.


# 3. Intents and Manifest

In the previous chapter, we learned about how XML and Java work together to constitute an activity. However if we want to create a full-fledged application, we'll need to build several activities, and we will need a way to switch between different activities and their corresponding screens.&#x20;

For our app to function in the Android operating system as expected, we would also need to understand how to manage our app’s status in the system. In this chapter, we’ll cover three important concepts that deal with these issues: lifecycle, intents, and permissions.

## Topics

* Activities
* Explicit Intents
* Implicit Intents
* Manifest
* Permissions
* Activity Lifecycle


# 3.1 Activities

In the previous chapter, we mentioned how each activity consists of a layout file controlling the design and a class file to help with the logic behind all the different GUI elements. So far in our application we’ve only been working with one `Activity` class, but an application can have several!

Let’s see an example of this in action with Eatery.&#x20;

![MainActivity (1) and CampusMenuActivity (2)](https://195521982-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LvOeSx5ZqjJA8sxykMu%2F-Lv_8l6DA7rFApJy3oar%2F-Lv_AIpRDpcnysFYStJu%2Fimage.png?alt=media\&token=09e35c90-789e-46bf-8768-d1cc21fd1df8)

The two different screens displayed here are each their own activities in the codebase. When the user clicks an Eatery card in `MainActivity`, we say we launch the second activity. In the `onCreate()` method of the second activity, we populate the `TextView` and wait times chart with the appropriate values. When the user clicks the back button in the second activity, then we say that the second activity has **finished**, and the display returns to `MainActivity` (which is the last unfinished activity).

## A Deeper Look

Returning to our project with the single `MainActivity` class, we can see that `MainActivity` extends `AppCompatActivity`, which is a built-in class that extends `android.app.activity`.&#x20;

This tells us that `MainActivity` is of type `Activity`, and so all of the `Activity` class methods become available for the programmer, including `setContentView()` and the overridden method `onCreate()`.


# 3.2 Implicit Intents

Implicit intents are used to launch other applications and processes on a user’s device that don’t belong to the same project when the exact destination is unknown.&#x20;

They declare a general action to perform and have the Android system itself take care of it. This includes the extremely common use of launching the default browser to open a link or clicking on the share icon to open any messenger app. These implicit intents are best used to enact functionality that your app itself does not contain.

In our example, we'll be creating a simple share feature. You'll notice that we create an `Intent` object, much like with explicit intents. We set the action of the intent, which in this case is a "send" action. There are many other options for actions, and Google provides a comprehensive list in their documentation. We also set the information to be sent and the information type.&#x20;

```kotlin
var sendIntent = Intent()
sendIntent.action = Intent.ACTION_SEND
sendIntent.putExtra(Intent.EXTRA_TEXT, textMessage)
sendIntent.type = "text/plain"
// Verify that there is at least one app that can receive this action
if (sendIntent.resolveActivity(packageManager) != null) {
    startActivity(sendIntent)
}
```

### Sending Data Through Intents

Sometimes it is necessary to pass information from one activity to another -- this is also done with intents. Notice in the above example how we can attach key-value pairs to our intent in the same Activity in which we declare the Intent, using the method `putExtra(key, value)` . This stores all of our data in something called a `bundle`!

When a `ACTION_SEND` request is made by the system, the phone's system will search through all applications on the device and open up a list of ones that are capable of receiving this action (e.g., Gmail, Messenger, Google Drive can all receive `ACTION_SEND` intents) and the user can select one of the choices.&#x20;

Intents give us a lot more functionality to create a positive experience for our user. Not only can we create and switch between multiple screens with explicit intents, but we can allow our user to easily navigate to specific applications with pre-filled data from the intent extras (like opening Google Maps with specific coordinates already filled in).&#x20;


# 3.3 Explicit Intents

Intents aren’t necessarily always implicit because sometimes we might want to open a specific process of a particular app. Explicit intents are used in such cases, i.e., when the destination is known.

In Android Native, explicit intents are used to request an action from another app component. However, when we're using Jetpack Compose, we don't need to use explicit intents for this purpose. Instead, we use explicit intents to perform a certain known action (for example, opening a specific app). They’re used when the destination is known, and the component is specified in the intent.

To launch another app, you need to know its package name and the fully qualified class name of the activity you want to open. After obtaining this information, we must create an intent to transition to the app with:

```kotlin
var intent = Intent(Intent.ACTION_VIEW)
```

Here what's in the parentheses is the activity action. `ACTION_VIEW` means the activity displays data to the user. Here's another example of an intent with a different activity action. In this intent, the action is to dial a contact number on your phone app.

```kotlin
var intent = Intent(Intent.ACTION_DIAL)
```

Next, we want to provide the intent with the necessary data. Say we want to open the Spotify app, we would add the following line of code next.

```kotlin
intent.data = Uri.parse("spotify:")
```

&#x20;Afterwards, whenever we want to actually transition (perhaps in the on click listener of a button), we launch the intent by running `startActivity(intent)` .&#x20;

```kotlin
context.startActivity(intent)
```

#### When to Use Explicit Intents in Jetpack Compose

You’d use an explicit intent in Compose when you want to:

* Open a specific service or activity in another app if you know the exact component to target.
* Perform tasks like launching a camera app, opening the dialer, or sending a message through a specific app
* Navigate from one `Activity` to another within your own app. (This is less common when we're using Jetpack Compose.)


# 3.4 Manifest

Every Android app, large or small, has a very important little file in the root folder called `AndroidManifest.xml` . This file describes crucial information about the app, including the application's name, the activities it holds, the permissions it requests, and the hardware and software requirements needed to run the app in the first place. Android Studio will handle the boilerplate code of the manifest when you first create the app, but often you will need to modify the file to suit your own needs.

The root element requires an attribute for your app's package name, which the build tools will use to resolve any relative classes in the package and generate the resources file.

{% code title="AndroidManifest.xml" %}

```markup
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.cornellappdev.android.pollo">
    ...
</manifest>
```

{% endcode %}

In the child `<application ...>` tag, these attributes specify project-specific information including where you set the app icon for your project.

Under the application tag is where most of the configuring will be done. For each activity that you create in your app, you **must** declare a corresponding XML activity element as a child of `<application ...>` in `AndroidManifest.xml`. Otherwise, that activity will never be seen by the system and can never be launched with an intent.&#x20;

An example manifest looks something like:&#x20;

{% code title="AndroidManifest.xml" %}

```xml
<manifest package="com.example.exampleproject" ... >
    <application ... >
        <activity android:name=".MainActivity">
            ...
        </activity>
    </application>
</manifest>
```

{% endcode %}

We can add a child element of type `intent-filter` to each `Activity` element. This is where an activity specifies what implicit intents the application can receive. In the manifest file for Google’s Gmail app, one of the activities would have the action `android.intent.action.SEND` under their intent filter, which allows the application to receive the implicit intent with action `Intent.ACTION_SEND`.

```markup
<activity ...>
    <intent-filter>
        <action android:name="android.intent.action.SEND" />
    </intent-filter>
</activity>
```

The example below is of an activity that is recognized by the system as the entry point to this application (labeled with `action.MAIN`), and as something that shows up and can be launched from the app drawer - the area in your phone where all installed apps are stored (labeled with `category.LAUNCHER`).&#x20;

{% code title="AndroidManifest.xml" %}

```markup
<activity android:name=".MainActivity" ... >
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>
```

{% endcode %}

The manifest file is a small but mighty beast that controls activities, permissions, and implicit intents. If this file becomes messed up, then some issues will likely *manifest* their way into your application. *Heh* 🤭


# 3.5 Permissions

The manifest file also controls what permissions your application has requested from the user. Apps must be granted the right to use certain functions of the device, from accessing the camera to accessing the internet. Only once the app has requested access, will the system grant the benign ones, and ask the user to explicitly hand over control of the more dangerous ones. For example, knowing the state of the network will be granted automatically, while knowing device location must go through the user first.

The app must publicize all required permissions in `AndroidManifest.xml`, where they all sit in `<uses-permission>` tags in the manifest root as a sibling child to the `<application>` element. The following line declares that this application will request the geo-location of users.&#x20;

```markup
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
```

When dealing with code that involves permissions, developers must be able to adequately respond to situations where the user has denied the permission to the application (and not crash because of an exception because the permission wasn’t granted).&#x20;

## How can we request permissions?

Before checking out this section, make sure you first check out [this](/chapters/3.-intent-and-manifest/3.2-implicit-intents#getting-a-result-from-an-activity)!

**Step 1: Declare the permission in** the[ **Android Manifest file**](https://developer.android.com/guide/topics/manifest/manifest-intro)**:** In Android, permissions are declared in the **AndroidManifest.xml** file using the **uses-permission** tag.

```xml
<manifest xlmns:android...>
 ...
 <uses-permission android:name=”android.permission.PERMISSION_NAME”/>
 <application ...
</manifest>
```

**Step 2:** **Check whether permission is already granted or not. If permission isn’t already granted, request the user for the permission**: In order to use any service or feature, the permissions are required. Hence we have to ensure that the permissions are given for that. If not, then the permissions are requested.

This step is compromised of multiple steps.

**Step 2.1: Register the permissions callback, which handles the user's response to the system permissions dialog.** In this case, we can use the ActivityResultLauncher [introduced during the explicit intents section](/chapters/3.-intent-and-manifest/3.2-implicit-intents#getting-a-result-from-an-activity) to automatically handle actually requesting the permission to the user and giving us the result:

```kotlin

    private lateinit var requestPermissionLauncher: ActivityResultLauncher<String>
    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        requestPermissionLauncher =
            registerForActivityResult(ActivityResultContracts.RequestPermission()
            ) { isGranted: Boolean ->
                if (isGranted) {
                    // Permission is granted. Continue the action or workflow in your
                    // app.
                } else {
                    // Explain to the user that the feature is unavailable because the
                    // features requires a permission that the user has denied. At the
                    // same time, respect the user's decision. Don't link to system
                    // settings in an effort to convince the user to change their
                    // decision.
                }
            }
        ...
    }
```

**Step 2.2: Check or request the given permission.** This code snippet demonstrates the recommended process of checking for a permission, and requesting a permission from the user when necessary:

```kotlin
when {
    ContextCompat.checkSelfPermission(
            <CONTEXT>, // Usually <this> if in an activity
            Manifest.permission.<REQUESTED_PERMISSION>
            ) == PackageManager.PERMISSION_GRANTED -> {
        // Permission is granted. Continue the action or workflow in your app.
    }
    // If the permission was denied previously, if requested again there will be 
    // a never ask again checkbox in the permission prompt. 
    // shouldShowRequestPermissionRationale checks to see if the 
    // user checked said checkbox. shouldShowRequestPermissionRationale 
    // method returns false only if the user selected never ask again 
    // or device policy prohibits the app from having that permission and true
    // otherwise.
    ActivityCompat.shouldShowRequestPermissionRationale(context, Manifest.permission.<REQUESTED_PERMISSION>) -> {
        // In an educational UI, explain to the user why your app requires this
        // permission for a specific feature to behave as expected. In this UI,
        // include a "cancel" or "no thanks" button that allows the user to
        // continue using your app without granting the permission.
    }
    else -> {
        // You can directly ask for the permission.
        // The registered ActivityResultCallback gets the result of this request.
        requestPermissionLauncher.launch(
                Manifest.permission.<REQUESTED_PERMISSION>)
    }
}
```

### How does this work if we want to request multiple permissions at once?

Similarly, multiple permissions can be requested at the same time by passing an array of Permissions instead of single permission as input and we get a MutableMap with permissions as keys and grant result as values in the activityResultCallback.

The request permission launcher changes as so:

```kotlin
    private lateinit var requestPermissionLauncher: ActivityResultLauncher<Array<String>>
    requestPermissionLauncher =
        registerForActivityResult(
           ActivityResultContracts.RequestMultiplePermissions())   
             { permissions ->
                // Handle Permission granted/rejected
               permissions.entries.forEach {
                  val permissionName = it.key
                  val isGranted = it.value
                  // Feel free to also check for in your 
                  // conditions the permissionName as well! Below is just 
                  // a bsaic example that only checks to see if it's granted.
                  if (isGranted) {
                     // Permission is granted
                  } else {
                    // Explain to the user that the feature is unavailable because the
                    // features requires a permission that the user has denied. At the
                    // same time, respect the user's decision. Don't link to system
                    // settings in an effort to convince the user to change their
                    // decision.
                  }
              }
              
              // Can also opt for something like this, which, 
              // if you need to check to see if multiple/all your permissions 
              // are granted before proceeding may be useful:
              if (permissions.entries.all {
                    it.value == true }) {
                    // All permissions are granted
              } else {
                 // Explain to user why you need to access all of these permissions!
              }
              
              // Since permissions is a map of the permissions to their isGranted
              // status, can also do something like this:
              if (permissions[Manifest.permission.<SOME_SPECIFIC_PERMISSION>] == true
              && permissions[Manifest.permission.<SOME_OTHER_PERMISSION>] == true) {
                 // The given permissions are granted
              } else {
                 ...
              }
           }
        }

```

**Ultimately just tailor the conditions to your needs!**

The when else block in step 2.2 is dependent on your needs of your permissions but the structure is relatively the same. If you need multiple permissions at once for some API / UI / functionality, expand the first if case to check to see if all the respective permissions are granted.

If I needed to access both the camera and external storage I could do something like this:

```kotlin
when {
    ContextCompat.checkSelfPermission(
        this,
        Manifest.permission.CAMERA
        ) == PackageManager.PERMISSION_GRANTED && 
    ContextCompat.checkSelfPermission(
        this,
        Manifest.permission.READ_EXTERNAL_STORAGE
        ) == PackageManager.PERMISSION_GRANTED  -> {
        // Permissions are granted, carry through with action
    }
    ...
 }
```

This process can be abstracted away as a function!

```kotlin
private fun hasPermissions(context: Context, vararg permissions: String): Boolean = permissions.all {
        ActivityCompat.checkSelfPermission(context, it) == PackageManager.PERMISSION_GRANTED
 }
 
 ...
 
 when {
        hasPermissions(this,
            Manifest.permission.READ_CONTACTS,
            Manifest.permission.READ_EXTERNAL_STORAGE) -> {
        // Permissions are granted, carry through with action
        }
        ...
}
```

When multiple permissions are requested, the permission prompts are shown one after another instead of all at once, so users could deny some but allow others, thus you must check if you shouldShowRequestPermissionRationale for each individual permission!

Lastly:

**How do we use the new launcher to request multiple permissions?**

Instead of passing a singular permission, we pass in an array of the permissions we want to ask for, here's an example below:

```kotlin
...
requestPermissionLauncher.launch(
arrayOf(Manifest.permission.CAMERA,    
        Manifest.permission.READ_EXTERNAL_STORAGE)             
) 
...
```

## Accompanist - Simplifying Permissions

Requesting permissions can be a complex process, but with the help of libraries like Accompanist, handling permissions becomes much easier and streamlined, especially in Jetpack Compose. Here, we introduce the [**Accompanist**](https://github.com/google/accompanist?tab=readme-ov-file) library, which simplifies permission handling in Compose-based applications.

### Installation

To get started with Accompanist Permissions, add the following dependency to your `build.gradle.kts` file:

{% code title="build.gradle" %}

```kotlin
dependencies {
    implementation("com.google.accompanist:accompanist-permissions:0.34.0")
}
```

{% endcode %}

### Setup

Declare any required permissions in the `AndroidManifest.xml`. We'll be working with the `ACCESS_FINE_LOCATION` and the `READ_CONTACTS` permissions which changes our manifest as so:

{% code title="AndroidManifest.xml" %}

```xml
<manifest xlmns:android...>
 ...
 <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
 <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
 <uses-permission android:name="android.permission.READ_CONTACTS" />
 
 <application 
 ...
 </application>
</manifest>
```

{% endcode %}

### Checking and Requesting Permissions

Accompanist makes checking and requesting permissions straightforward by using `rememberPermissionState`. Permissions are managed as states in Compose, allowing UI updates based on permission status.

Here’s an example of how to request permissions for fine location and contacts:

{% code title="MainActivity.kt" %}

```kotlin
fun PermissionScreen() {
    // Permission states for location and contacts
    val locationPermissionState = rememberPermissionState(Manifest.permission.ACCESS_FINE_LOCATION)
    val contactsPermissionState = rememberPermissionState(Manifest.permission.READ_CONTACTS)

    // Handle UI based on permission status
    Column {
        Button(onClick = {
            if (!locationPermissionState.hasPermission) {
                locationPermissionState.launchPermissionRequest()
            }
        }) {
            Text("Request Location Permission")
        }

        Button(onClick = {
            if (!contactsPermissionState.hasPermission) {
                contactsPermissionState.launchPermissionRequest()
            }
        }) {
            Text("Request Contacts Permission")
        }

        // Example of showing rationale if permission is denied
        if (locationPermissionState.shouldShowRationale) {
            Text("We need location permission to show nearby places.")
        }

        if (contactsPermissionState.shouldShowRationale) {
            Text("Contacts permission is needed to access your contacts.")
        }
    }
}
```

{% endcode %}

#### Requesting Permissions Flow

1. **Check Permission State**: Use `rememberPermissionState` to check if a permission is granted or not.
2. **Launch Permission Request**: If a permission is not granted, call `launchPermissionRequest()` to show the system permission dialog.
3. **Handle Rationale**: If the user denies the permission, `shouldShowRationale` can be used to show a rationale message explaining why the app needs the permission.

**All done! Way more simple!**


# 3.6 Summary

There was a time when the act of writing a program gave the developer full control over each and every transistor of a machine. That era died alongside the 3.5” floppy disk.&#x20;

An app is a restricted bit of code, governed by the many laws set by the operating system that gives it power. Just as the Android API provides the means by which your app will work for the user, it also places limits upon what your app can do for the safety of the user. That is the underlying principle behind the many convoluted rules surrounding intents and the manifest file, avenues through which the application will request for additional power from the operating system.&#x20;


# 4. Navigation

How do we move 😭


# 4.1 Types of Navigation

Often times we don't think about how navigation within mobile apps work because the navigation cues feel so intuitive, but if we were to look closely at Android apps, you'll notice that there are several different ways to navigate between activities and fragments, and each technique has its own benefit and use cases.&#x20;

### Bottom Navigation Bar

The bottom navigation bar is one of the most common navigation system and it is present in almost all mobile apps. It is often used as the highest level of navigation. Here are some examples of what a bottom navigation bar looks like:&#x20;

![Examples of bottom navigation bar](https://195521982-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LvOeSx5ZqjJA8sxykMu%2F-MJhyMBA9fKE8MTsG1Fg%2F-MJi19OUGxTYefBc2Uex%2Fimage.png?alt=media\&token=9194907d-c85c-40d7-9f34-170fd1f2c5f1)

The bottom navigation bar is great for switching between different fragments that each independently serve as one topic or page of the app. Generally, the most frequently visited page is featured on the left or center and the user profile is featured at the right.&#x20;

Generally, the user interaction to move between different pages using the bottom navigation bar is through **tapping** the icons. If the no other navigation systems on the page, the app may sometimes allow for swiping to switch between pages, though this is more rare. Visual cues, like highlighting the current fragment/screen, are also present since the bottom navigation bar persist across the lifecycle of all these different screens.&#x20;

### Tab Layout&#x20;

Many apps with complex fragments will often also feature a top tab layout for even more granularity of pages and topic breakdown as the secondary navigation system.&#x20;

![Examples of tab layout](https://195521982-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LvOeSx5ZqjJA8sxykMu%2F-MJhyMBA9fKE8MTsG1Fg%2F-MJi3TnKGygU3fUQNprN%2Fimage.png?alt=media\&token=1c4bb99b-8a98-4aa7-bd20-ff12a2e86fbf)

With the two examples above, we can see that the apps feature both a bottom navigation bar and a tab layout at the top. The tab layout provides clear organization for pages that *only* pertain to an single fragment, and it is often triggered through a **swiping** gesture. Having different gestures for the bottom navigation and tab layout makes changing different fragments easier and more intuitive. There is also often a sliding indicator to notify the user which page they are on.&#x20;

You'll notice that there are apps that break this pattern like Eatery:&#x20;

![Eatery navigation pill ](https://195521982-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LvOeSx5ZqjJA8sxykMu%2F-MJhyMBA9fKE8MTsG1Fg%2F-MJi6XA37pTkHuE0pOZ3%2Fimage.png?alt=media\&token=4515a098-b433-4cb1-b033-006bf9cd8944)

In Eatery, the secondary navigation is to switch between campus and Collegetown eateries is featured at the bottom through a button pill, and it is triggered through clicking the pill rather than swiping. It can be argued that a swiping interaction may be more intuitive, but designing the navigation this way isn't wrong either. This is just to show that there is not one right way to design a navigation system.&#x20;

### Drawer Layout

Many apps will also feature a hamburger menu that contains a ton of different hidden pages and features that are nice to have but are not crucial to the app's main functionality. This is because the user takes more "effort" to trigger the hamburger menu to open and thus, are less likely to access the menu. Furthermore, the pages are hidden from sight, so the user will be less likely to memorize what pages are available to them in this menu.&#x20;

Here are some examples of hamburger menus in apps:&#x20;

![Examples of hamburger menus](https://195521982-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LvOeSx5ZqjJA8sxykMu%2F-MJhyMBA9fKE8MTsG1Fg%2F-MJi86JjC7my4votmwAO%2Fimage.png?alt=media\&token=b5daca8b-f2be-4245-a501-bc2a1dc96edc)

Generally, hamburger menus will only cover a portion of the screen (where the uncovered screen is greyed out) and it will feature pages that the user may not need to access everyday. This is also usually where the navigation to the setting page is stored.&#x20;

You'll notice in the screenshot that Uber has its hamburger menu on the left while Instagram has coming from the right. Several years ago, almost all hamburger menus in Android apps came from the left, but recently, Google changed the Android OS navigation system to emulate Apple's "swipe to go back" gesture. This change disrupted many Android apps since the hamburger menu is usually triggered through a **swiping** gesture, and users were often confused if they swiped, and a menu came out instead of the app "going back" a page. As such, many apps moved their hamburger menu to the right or removed the menu altogether.&#x20;


# 4.2 Implementation of the Bottom Navigation Bar

The bottom navigation bar is one of the most common type of navigation system in Android apps. Implementing it in Jetpack Compose does require a bit of diligence though. This textbook page should help walk you through the process nicely and explain some of the code in more detail than the demo.&#x20;

{% hint style="info" %}
Much of this tutorial can also be applied for basic Compose navigation even if you aren't using a navbar, so if that is your use case, feel free to read on!
{% endhint %}

### Step 0: Dependencies:

To start, you'll need to add the following in your `libs.versions.toml` file to specify the library versions. Under `[libraries]` and `[plugins]`, add the corresponding lines:

<pre class="language-toml" data-title="libs.versions.toml"><code class="lang-toml"><strong>[libraries]
</strong><strong># ...
</strong><strong>androidx-compose-navigation = { group = "androidx.navigation", name = "navigation-compose", version="2.8.0-beta06"}
</strong>kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version = "1.6.3" }

[plugins]
# ...
jetbrains-kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
</code></pre>

Then, in `build.gradle` file, add the following lines:

<pre class="language-kotlin" data-title="build.gradle (Module: app)"><code class="lang-kotlin"><strong>plugins {
</strong><strong>    // ...
</strong><strong>    alias(libs.plugins.jetbrains.kotlin.serialization)
</strong><strong>}
</strong><strong>
</strong><strong>dependencies {
</strong><strong>    // ...
</strong>    implementation(libs.androidx.compose.navigation)
    implementation(libs.kotlinx.serialization.json)
<strong>}
</strong></code></pre>

### Step 1: Creating the Screen sealed class&#x20;

When using type-safe navigation with compose, it's helpful to have one parent `sealed class` called  `Screen` that we can use in our app. This way we instantly know which `data classes` are screens and which ones represent data within our app. Create a `sealed class`, and create a couple subclasses that extend it with some routes you want your app to have. It could be a good idea to putt this class in its own file.&#x20;

We will annotate each of these classes with `@Serializable`, since we want Compose navigation to be able to pass these screen objects between activities, so we need to let the compiler know that these can be converted to strings.&#x20;

We're also going to make `ProfileScreen` a `data class`, this will allow us to pass arguments to the screen. We specify a mandatory `userId` argument here, so whenever someone loads the `ProfileScreen`, they need to pass a `userId`. If you want to pass a custom object between screens, I recommend checking out [this video](https://youtu.be/qBxaZ071N0c) (although it's rare that you would actually need to do this).&#x20;

{% code title="Screen.kt" %}

```kts
@Serializable
sealed class Screen {
    @Serializable
    data object HomeScreen : Screen()

    @Serializable
    data object SettingsScreen : Screen()

    @Serializable
    data class ProfileScreen(val userId: String) : Screen()
    // ...
}
```

{% endcode %}

### Step 2: Initializing a NavHost

The `NavHost` will select the correct screen to display based on the current route in its `navController`. It will also specify which route the app starts at. Let's initialize a `navController` and the corresponding `NavHost` . You may notice that we are using a `Scaffold` here. A `Scaffold` is a layout composable that helps us arrange a bottom bar and main content, so this is only for if you want to use bottom navigation. Also note that we wrapped our content in a `Box` that uses `innerPadding`. This is just to make sure that the content is not behind the `bottomBar` composable.

```kotlin
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        // ...
         
        setContent {
            FromScratchNavigationTheme {
                val navController = rememberNavController()

                Scaffold(modifier = Modifier.fillMaxSize(), bottomBar = {
                    // TODO we will create our bottom navigation bar here
                }) { innerPadding ->
                    Box(modifier = Modifier.padding(innerPadding)) {
                        NavHost(
                            navController = navController,
                            startDestination = Screen.HomeScreen
                        ) {
                            composable<Screen.HomeScreen> {
                                Text(text = "HOME: add your screen here")
                            }
                            composable<Screen.SettingsScreen> {
                                Text(text = "SETTINGS: add your screen here")
                            }
                            composable<Screen.ProfileScreen> {
                                Text(text = "PROFILE: add your screen here")
                            }
                        }
                    }
                }
            }
        }
    }
}
```

### Step 3: Defining a bottom tab

Each app could have its own notion of a "bottom tab", depending on how you want it to look and what information you need to store. For this demo, I'm going to assume that we want each bottom tab to have an icon, label, and screen. So I am going to represent this with a `data class`.

```kotlin
data class NavItem(
    val screen: Screen,
    val label: String,
    val icon: ImageVector
)
```

Then, we need to specify the data of our tabs with a list. This should be held in state in the `ViewModel`, but for now we can just store the list of tabs in the `onCreate` method. Here are the tabs I made for this tutorial:

```kotlin
val tabs = listOf(
            NavItem(
                label = "Home",
                icon = Icons.Filled.Home,
                screen = Screen.HomeScreen,
            ),
            NavItem(
                label = "Settings",
                icon = Icons.Filled.Settings,
                screen = Screen.SettingsScreen,
            ),
            NavItem(
                label = "Profile",
                icon = Icons.Filled.Person,
                screen = Screen.ProfileScreen,
            )
        )
```

### Step 4: Adding the navigation bar

To create the navigation bar, we're going to use the `bottomBar` parameter of the `Scaffold` layout and pass in a `NavigationBar` composable. We map our list of `tabs` to actual composable functions that display the tabs. We can use `NavigationBarItem` for this. The updated code now looks as follows:

```kotlin
Scaffold(modifier = Modifier.fillMaxSize(), bottomBar = {
            NavigationBar {
                tabs.map { item -> 
                    NavigationBarItem(
                        selected = false,  // TODO
                        onClick = { navController.navigate(item.screen) },
                        icon = { Icon(imageVector = item.icon, contentDescription = null) },
                        label = { Text(text = item.label) }
                    )
                }
            }
        }
    )
```

However, we still have one problem. How do we know if a `NavigationBarItem` is selected? For this, we want to use the `navBackStackEntry`. This is a variable that gives us information about the top of the navigation stack. To use this, we will want to start by creating an extension function in our `Screen` class that allows us to convert from a nav backstack entry to a `Screen`.

```kotlin
sealed class Screen {
    // ...
    fun NavBackStackEntry.toScreen(): Screen? =
        when (destination.route?.substringAfterLast(".")?.substringBefore("/")) {
            "HomeScreen" -> toRoute<HomeScreen>()
            "SettingsScreen" -> toRoute<SettingsScreen>()
            "ProfileScreen" -> toRoute<ProfileScreen>()
            else -> null
        }
}
```

Behind the scenes, a route name might look like `"com.example.demo.ui.Screen.Profile/{profileId}"`, so to get the name of the screen, we look at the substring after the last `.` . Sometimes routes will have arguments passed to them, so we also have to look before the first `/` to find the screen name. We then use the `toRoute` function to automatically parse this route and convert it to its `data class` representation.

Then we can track the screen as follows:

```kotlin
val navBackStackEntry = navController.currentBackStackEntryAsState().value


Scaffold(modifier = Modifier.fillMaxSize(), bottomBar = {
    NavigationBar {
        tabs.map { item ->
            NavigationBarItem(
                selected = item.screen == navBackStackEntry?.toScreen(),
                onClick = {
                    navController.navigate(item.screen)
                },
                icon = { Icon(imageVector = item.icon, contentDescription = null) },
                label = { Text(text = item.label) }
            )
        }
    }
})
```

### Step 5: Future Customization

Congrats, you implemented a bottom navigation bar in Jetpack Compose! Here are some tips to help you with general navigation skills:

#### Navigating to other screens

Let's say I wanted to navigate to a `ProfileDetails` screen from my `Profile` screen, and I wanted my `Profile` screen to receive an `id`.&#x20;

It's recommended that we pass the navigation action in a lambda to `ProfileScreen`, instead of giving it access to the entire `navController`. The reason is, if we put `navController` as a parameter for `ProfileScreen`, then that makes `ProfileScreen` difficult to test. If we wanted to try to test `ProfileScreen` in isolation, we'd need to provide a mock navigator to it, instead of just being able to provide an empty lambda for navigation to other screens. Not only that, but it makes it harder to reason about the behavior of `ProfileScreen` , because it can use `navController` however it wants. So the code looks like this:

```kotlin
NavHost(
    // ... 
) {
    composable<Screen.Home> {
        HomeScreen(navigateToProfileDetails = { id ->
            navController.navigate(Screen.ProfileDetails(id))
        })
    }
    // ...
}
```

#### Receiving navigation arguments

To receive navigation arguments on a certain screen, we want to use the `NavBackStackEntry`. The `composable` function in `NavHost` actually takes a parameter of type `@Composable() ((NavBackStackEntry) -> Unit)`, meaning that inside the composable function, the parameter we have access to is the `NavBackStackEntry`. This is more easily understood through example:

```kotlin
NavHost(
    // ... 
) {
    composable<Screen.ProfileDetails> { navBackStackEntry ->
        val profileId = navBackStackEntry.toRoute<Screen.ProfileDetails>().profileId
        ProfileDetails(profileId)
    }
    // ...
}
```

Whenever `NavHost` maps the current navigation destination to a `composable`, that `composable` provides `navBackStackEntry`. We can then get the arguments that we're looking for by using the `toRoute` function, which takes the back stack entry, and converts it to an instance of our `Screen.ProfileDetails` data class. We can access the fields of this data class, so that includes `profileId`. Then we are able to pass this as a parameter to our hypothetical `ProfileDetails` screen.&#x20;


# 5. Data and Persistent Storage

we storing !1!

This module will deal with persistent storage to the Android phone, as well as storage across an application session (from open to close of an app). These topics will be particularly useful for developing applications that are client facing, though you will see in the future that most meaningful application data is stored online rather than locally.

### Topics

* Shared Preferences
* Rooms
* Entities
* Data Access Objects
* Databases


# 5.1 Singleton Classes

A singleton is a design pattern that restricts the instantation of a class to only one instance; a singleton class is a class that is defined in such a way that only one instance of the class can be created and used everywhere.

This is used when creating a central point of access for an application to access its data store. In Android this is very conveinient as many times we need an application to be able to access a central in-sync data store location from across many differant activities or fragments.

## Singleton Class Attributes:

1. **Only one instance:** The singleton class has only one instance. Also, outer classes and subclasses should be prevented to create the instance.
2. **Globally accessible:** The instance of the singleton class should be globally accessible so that each class can use it.

## **Rules for making a class Singleton**

The following rules are followed to make a Singleton class:

1. A private constructor
2. Globally accessible object reference
3. Consistency across multiple threads&#x20;

### Implementation

We can leverage using the object keyword. In Kotlin, `object` is a special class that only has one instance. If you create a class with the `object` keyword instead of `class`, the Kotlin compiler makes the constructor private, creates a static reference for the object, and initializes the reference in a static block, automatically fulfilling the singleton contract.

Contrary to a **`class`**, an **`object`** can’t have any constructor, but **`init`** blocks are allowed if some initialization code is needed.

Here's an example singleton of a simple counter:

```kotlin
object Counter {
    var counter: Int
        // Only allows retrival of the counter, 
        // setting restricted to only within Singleton
        private set
    
    // Optional init block if you need some initalization
    init {
        counter = 0
    }

    // Increments the counter and returns the new number after incrementing.
    fun incrementCounter(): Int {
        return ++counter
    }
    
    // Can define whatever member functions you want!
}
```

Now, the singleton can be easily invoked by writing the below code:

```kotlin
val counter = Counter
counter.incrementCounter()
```

`object` comes with a limitation: object declarations can not have constructors which means they can not take parameters.

#### What if we want to take in arguments?

We can leverage companion object to create a singleton class! In short, companion objects are singleton objects whose properties and functions are tied to a class but *not* to the instance of that class — basically like the “static” keyword in Java but with a twist.

Here's the equivalent object as a singleton class:

```kotlin
class Counter private constructor(private var counter: Int) {   
    fun incrementCounter(): Int {
        return this.counter++
    }
    
    // The only way to instantiate Counter is through our companion object!
    companion object {
        private var instance: Counter? = null
        
        // Use of synchronized helps prevent race conditions (which you may know
        // what this is if you've taken an operating systems class, if not don't
        // worry too much!)
        fun getInstance(param: Int): Counter = instance ?: synchronized(this) {
            instance ?: Counter(param).also { instance = it }
        }
    }
}
```

**Key things to note:**

* **Make sure that your constructor is private**
* **Add any data you are interested in as an argument to the constructor, expand the definition of getInstance if so.**

Now, the singleton can be easily invoked by writing the below code:

```kotlin
val counter = Counter.getInstance(0)
counter.incrementCounter()
```


# 5.2 Shared Preferences

One of the most Interesting Data Storage options **Android** provides its users is **Shared Preferences**. **Shared Preferences** is the way in which one can store and retrieve small amounts of primitive data as key/value pairs to a file on the device storage such as String, int, float, Boolean that make up your preferences in an XML file inside the app on the device storage. It's commonly used for things such as login credentials, favorites, and other in-app settings.&#x20;

### Opening Shared Preferences

You can create a new shared preference file or access an existing one by calling one of these methods:

[`getSharedPreferences()`](https://developer.android.com/reference/android/content/Context#getSharedPreferences\(java.lang.String,%20int\)) — Use this if the values you are storing need to be accessed from different activities in the app.&#x20;

```kotlin
val sharedPreference =  getSharedPreferences("PREFERENCE_NAME", Context.MODE_PRIVATE)
```

* Android allows you to have several shared preferences files, and so each must be uniquely identified with a key. In the above example, `"PREFERENCE_NAME"` is the key. If you only need one Shared Preferences across your app, use this same key everywhere you use `getSharedPreferences(...)`
* The second argument defines the access to the preferences file. `Context.MODE_PRIVATE` means that only your app will be able to access the file, and is most often the desired setting.

[`getPreferences()`](https://developer.android.com/reference/android/app/Activity#getPreferences\(int\)) — Use this if the values you are storing are only accessed and written from one activity. In this way, things written here tend to be more specific to one part of your app.&#x20;

```kotlin
val sharedPreference =  getPreferences(Context.MODE_PRIVATE)
```

* Unlike `getSharedPreferences`, you do not need to provide a key for this shared preferences file since it is unique to the activity

### Setting Preferences

Each value you store must have an associated key. This key must be unique to the value. The `SharedPreferences.Editor()` is used to edit values in the `SharedPreferences`. We can call `commit()` or `apply()` to save the values in the SharedPreferences file. The `commit()` saves the values immediately whereas `apply()` saves the values asynchronously.

Setting values are quite simple; here is how to set an int:

```kotlin
val sharedPreference =  getSharedPreferences("PREFERENCE_NAME", Context.MODE_PRIVATE)
val editor = sharedPreference.edit()
editor.putInt("KEY", 10)
editor.commit()
```

![Showcases the permitted types on a SharedPreference instance](https://195521982-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LvOeSx5ZqjJA8sxykMu%2Fuploads%2FmTTlC1a1IDmupmWCUgfy%2Fimage.png?alt=media\&token=7938902a-fb39-483c-8274-2ef160e598f2)

### Reading Preferences

Getting values is similarly straightforward. To retrieve a value, you must use the same key you used to set it. To retrieve the above value:

```kotlin
val sharedPreference =  getSharedPreferences("PREFERENCE_NAME", Context.MODE_PRIVATE)
sharedPreference.getInt("KEY", 0)
```

The second argument to getInt is the `defaultValue`, which is the value that will be returned in the case that nothing has been set for the key provided.


# 5.3 Rooms

But what if we want to store more complex data structures? Sure you could find a way to hackily do that using `SharedPreferences`, but doing so can introduce bugs and is generally poor style. Android's solution is the `Room` library.

The Room persistence library provides an abstraction layer over SQLite to allow fluent database access while harnessing the full power of SQLite.

To start, you must add the following dependencies into your app's `Build.gradle` file:

```groovy
apply plugin: "kotlin-kapt"

dependencies { 
    implementation "androidx.room:room-runtime:$room_version"
    implementation "android.arch.persistence.room:runtime:1.0.0"
    annotationProcessor "android.arch.persistence.room:compiler:1.0.0"
    implementation "androidx.legacy:legacy-support-v4:1.0.0"
    kapt "androidx.room:room-compiler:$room_version"
}
```

And the following to your Project's `Build.gradle`:

```groovy
buildscript {
  ext.room_version = '2.1.0-alpha01'
}
```

There are three major components in Room:

* Data entities that represent tables in your app's database.
* Data access objects (DAOs) that provide methods that your app can use to query, update, insert, and delete data in the database.
* The database class that holds the database and serves as the main access point for the underlying connection to your app's persisted data.

The database class provides your app with instances of the DAOs associated with that database. In turn, the app can use the DAOs to retrieve data from the database as instances of the associated data entity objects. The app can also use the defined data entities to update rows from the corresponding tables, or to create new rows for insertion. Figure 1 illustrates the relationship between the different components of Room.

![Figure 1](https://195521982-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LvOeSx5ZqjJA8sxykMu%2Fuploads%2FYgoWpRUt886l2gBSzOIC%2FMask%20group%20\(8\).png?alt=media\&token=82509292-72a6-466b-8b7b-15bdd86881b7)

Next, we will introduce these three components more in-depth!


# 5.4 Entities

When you use the Room persistence library to store your app's data, you define entities to represent the objects that you want to store. Each entity corresponds to a table in the associated Room database, and each instance of an entity represents a row of data in the corresponding table.

### Anatomy of an entity <a href="#anatomy" id="anatomy"></a>

You define each Room entity as a class that is annotated with `@Entity`. A Room entity includes fields for each column in the corresponding table in the database, including one or more columns that comprise the primary key (Each Room entity must define a primary key that uniquely identifies each row in the corresponding database table. The most straightforward way of doing this is to annotate a single column with `@PrimaryKey)`.

The following code is an example of a simple entity that defines a GroceryItem table:

```kotlin
@Entity // Can use @Entity(tableName = "<CUSTOM_NAME>") to change the table name.
data class GroceryItem(@PrimaryKey val item: String, 
                       val brand: String,
                       @ColumnInfo(name = "cost") val number: Int // Can use @ColumnInfo to define your column name differently.
                       )
```

Here, each item in our grocery list has 3 fields: a name, a brand, and the number needed.&#x20;

{% hint style="info" %}
**Note:** To persist a field, Room must have access to it. You can make sure Room has access to a field either by making it public or by providing getter and setter methods for it.
{% endhint %}


# 5.5 Data Access Objects

When you use the Room persistence library to store your app's data, you interact with the stored data by defining *data access objects*, or DAOs. Each DAO includes methods that offer abstract access to your app's database.

### Anatomy of a DAO <a href="#anatomy" id="anatomy"></a>

You can define each DAO as either an interface or an abstract class. For basic use cases, you should usually use an interface. In either case, you must always annotate your DAOs with [`@Dao`](https://developer.android.com/reference/kotlin/androidx/room/Dao). DAOs don't have properties, but they do define one or more methods for interacting with the data in your app's database.

Here is an example DAO for our grocery item entity:

```kotlin
@Dao
interface GroceryItemDao {
    // Get all GroceryItemEntitys
    @Query("SELECT * FROM groceryitementity")
    fun getAll(): List<GroceryItemEntity> 

    // Get all GroceryItemEntitys who's name is in the list `items`
    @Query("SELECT * FROM groceryitementity WHERE item IN (:items)")
    fun loadAllByIds(items: List<String>): List<GroceryItemEntity>

    // Insert the given GroceryItemEntity
    @Insert
    fun insertAll(vararg users: GroceryItemEntity)
    
    // Update the GroceryItemEntity given. Uses the primary key to match up the Entity
    @Update
    fun update(groceryItem: GroceryItemEntity)

    // Delete the given GroceryItemEntity by the primary key
    @Delete
    fun delete(groceryItem: GroceryItemEntity)
}
```

{% hint style="info" %}
You can additionally do more complex queries, deletions, updates, and insertions using more advanced SQL. For many cases, however, this will do. Feel free to check out the backend course for [a quick rundown](https://backend-course.cornellappdev.com/chapters/databases/lecture#sql-operation-examples) of the SQLite language!
{% endhint %}

Note: Since primary keys must be unique to update an item that already exists in the database, you *must* use an update method, not an insert method. It will throw an exception if an insert is called with a primary key that already exists in the database.


# 5.6 Databases

### Creating the Database

The database does the heavy lifting of storing and retrieving your entities through the abstract functions of your DAO.&#x20;

The following code defines an `GroceryDatabase`class to hold the database. `GroceryDatabase`defines the database configuration and serves as the app's main access point to the persisted data. The database class must satisfy the following conditions:

* The class must be annotated with a [`@Database`](https://developer.android.com/reference/kotlin/androidx/room/Database) annotation that includes an [`entities`](https://developer.android.com/reference/kotlin/androidx/room/Database#entities) array that lists all of the data entities associated with the database.
* The class must be an abstract class that extends [`RoomDatabase`](https://developer.android.com/reference/kotlin/androidx/room/RoomDatabase).
* For each DAO class that is associated with the database, the database class must define an abstract method that has zero arguments and returns an instance of the DAO class.

```kotlin
@Database(entities = [GroceryItem::class], version = 1)
abstract class GroceryDatabase: RoomDatabase() {
    abstract fun groceryItemDao(): GroceryItemDao
}
```

### Accessing the Database

After you have defined the data entity, the DAO, and the database object, you can use the following code to create an instance of the database:

```kotlin
val db = Room.databaseBuilder(
            applicationContext,
            GroceryDatabase::class.java, "grocery-database"
        ).build()
```

{% hint style="danger" %}
If you are using your database across your app, you should only need one instance of the Room Database class. Having the number of instances of a Database can lead to memory leaks. Therefore one should use the [singleton ](/chapters/7.-persistent-storage/6.1-singletons)patterns in the Database to keep one unified instance!
{% endhint %}

From there, you can call any of the methods defined in your DAO:

```kotlin
val groceryItemDao = db.groceryItemDao()
groceryItemDao.insert(groceryItem)
val groceryItems = groceryItemDao.getAll()
...
```

Note: since database access can take a long time, we want to run it in the background--otherwise the UI thread can be blocked, causing the app to freeze or just be generally slow. The Rooms library can be combined with [Coroutines ](/chapters/5.5-concurrency/7.2-coroutines)to help mitigate this issue!


# 5.5 Concurrency

Doing multiple things at the same time! We love multi-tasking 🤩


# 5.5.1 Coroutines

A coroutine is a concurrency design pattern that you can use on Android to simplify code that executes asynchronously.

If you were to run a networking request on its own, there can be many things that goes wrong. Perhaps the networking request takes a long time to complete, and the user is left waiting. Perhaps the networking request fails, but the application has no way to detect and handle the failure. This is where coroutines come into play.&#x20;

## Blocking vs Suspending Functions

One of the most important characteristics of coroutines is that it supports suspending functions. Normally when a networking call is made out without some sort of wrapper, it is a blocking call where it blocks all other operations on the thread unless the call is completed. This is not ideal as we don't want to leave the user hanging.&#x20;

In the example below, Function A and Function B are blocking calls where Function B cannot run until Function A has been completed.&#x20;

![Blocking function diagram](https://195521982-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LvOeSx5ZqjJA8sxykMu%2F-ML8z33WkLFu6J6LCp7m%2F-ML9PPtK6HquWSn9GgQZ%2Fimage.png?alt=media\&token=201c55c0-7d9f-4c3e-a6de-a9450ddb426a)

However, with coroutines, we can build suspended calls where several functions can run concurrently. In the example below, even if Function A is started first, it will suspend itself to allow Function B to run and finish before unsuspending and finishing. The great thing about coroutines is that it'll figure out the order of suspending functions to optimize the runtime for developers! &#x20;

![Suspending function diagram](https://195521982-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LvOeSx5ZqjJA8sxykMu%2F-ML8z33WkLFu6J6LCp7m%2F-ML9PVQVDhgFtQxbJTGU%2Fimage.png?alt=media\&token=7023b7c6-0763-4ba6-9a9c-06c27837badf)

Figure 1 and 2 [source](https://medium.com/mobile-app-development-publication/understanding-suspend-function-of-coroutines-de26b070c5ed#:~:text=BLOCKING%3A%20Function%20A%20has%20to,not%20locked%20by%20Function%20A.)


# 5.5.2 Implementation of Coroutines

## Implementation of Coroutines

Here is a basic example of a function built with coroutines:&#x20;

```kotlin
val job = GlobalScope.launch {
    delay(1000L)
    println("World!")
}
println("Hello,")
job.join() // waits until the job is completed before it outputs 

// Output: 
// Hello, 
// World 
```

As we see from the example, `job` is a coroutine that we declare first, but it does not output until we call `job.join()`. This allows us to run functions ahead of time and only output them when needed.&#x20;

The issue with the code above is that it uses `GlobalScope`, which acts like a top-level thread. If the code inside of the `GlobalScope` consumes a lot of resources, we don't want the operation to run at the top-level in case it blocks the rest of the app from functioning. Instead, we want to be using `CoroutineScope.`&#x20;

In the example below, we'll present a code block that uses `CoroutineScope` as well as two different functions `launch {...}` and `runBlocking {...}`. Both `runBlocking` and `coroutineScope` will create a new coroutine scope and waits for its children to finish before completing; however, the difference is that `runBlocking` will **block** operations until it finishes and `coroutineScope` only **suspends**, thus allowing other functions to use the thread.&#x20;

```kotlin
fun main() = runBlocking { // Creates coroutine scope A
    launch { // Creates coroutine scope B
        delay(200L)
        println("Task from runBlocking")
    }
    
    coroutineScope { // Creates a coroutine scope C
        launch { // Creates a coroutine scope D
            delay(500L) 
            println("Task from nested launch")
        }
    
        delay(100L)
        println("Task from coroutine scope") 
    }
    
    println("Coroutine scope is over") 
}
// Output: 
// Task from coroutine scope
// Task from runBlocking
// Task from nested launch
// Coroutine scope is over
```

In the example above, we can see that the `runBlocking` forces the app to block on `main()` until all its operations are completed. We can also see that code from **coroutine scope C** starts to run before suspending and allowing the code within **coroutine scope B** to complete and returning to the nested coroutine scope within **C**.&#x20;

Coroutines are extremely powerful in this sense because you can theoretically run thousands of coroutines at the same time, and the app won't crash.&#x20;

```kotlin
suspend fun main() {
    coroutineScope {
        repeat(1_000) { 
            launch { functionA() }
        }
    }
}

// Output: 
// functionA will run 1000 times. 
```

## Implementation with Suspending Functions

Generally, we want to be creating suspending functions to free up space in memory and allow the processors to determine which functions to complete first (unless there is a need for blocking functions). Below is an example on how to build a suspending function:&#x20;

```kotlin
fun main() = runBlocking {
    launch { functionA() }
    println("Hello,")
}

suspend fun functionA() { // keyword suspend is important
    delay(1000L)
    println("World!")
}

// Output: 
// Hello, 
// World!
```

In the example above, we use the keyword `suspend` to specify our suspending function. We can see from the example that code in the outer scope of `runBlocking` is ran first before the code inside of `launch` due to suspension.&#x20;


# 5.5.3 Coroutines with Networking Calls

## Coroutines with Networking Calls

All Kotlin coroutines run on dispatchers, and there are three types: `Main`, `IO`, and `Default`. It is perfectly okay to run suspending function on the main thread as long as the functions do not take a long time. Generally, networking and file IOs calls are ran on the IO thread to prevent consuming all the resources on the main thread.&#x20;

Here are the general guidelines for which dispatcher to use:&#x20;

| Dispatcher Type | Responsibilities                                                                                 |
| --------------- | ------------------------------------------------------------------------------------------------ |
| Main            | <ul><li>updating states</li><li>callings <code>suspend</code> and UI related functions</li></ul> |
| IO              | <ul><li>making networking requests</li><li>reading and writing to a file or database</li></ul>   |
| Default         | <ul><li>CPU intensive operations like parsing JSON or sorting a list</li></ul>                   |

Here's an example of how to make a networking call with coroutines:&#x20;

```kotlin
// Dispatchers.Main
suspend fun functionA() {
    val result = get("developer.android.com")
    show(result)
}

suspend fun get(url: String) = 
    // Dispatchers.IO
    withContext(Dispatchers.IO) { actualNetworkingCall() }

```

As we see in the example, the main function `functionA()` runs on the main thread, but the actual networking call occurs on the IO thread. This allows the outer function to complete other tasks as it waits for the networking call to complete itself.&#x20;

Example code blocks adapted from <https://kotlinlang.org/>.&#x20;


# 6. Networking and 3rd Party libraries

Think of the number of apps on your phone that doesn't require internet access in one way or another. Now take out all the ones that came pre-installed with the phone. Chances are, the list has decreased to zero or you can list them all on one hand. You're building apps for smartphones, devices that are, by definition, built to connect to a network and interface with it. Therefore, knowing how to implement networking is critical to your success as a developer.

Before we begin, note that your app requires the following permissions in the manifest to able to make networking calls:

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

## Topics

* HTTP Overview
* Coroutines
* Popular Networking Libraries


# 6.1 HTTP Overview

HTTP is a very common protocol used to transmit data over the Internet. It defines the format of messages and the actions that should be completed for those types of messages. While the messaging implementation can get very complicated, HTTP protocols make the task much easier for us to complete.

## Common HTTP Requests

To receive or send data, we make HTTP requests. Here are some of the more commonly used HTTP requests that you may want to use

| HTTP Request | Description                                                                      |
| ------------ | -------------------------------------------------------------------------------- |
| GET          | Retrieves information from the internet (ie. getting information from Wikipedia) |
| POST         | Sends data to the internet (ie. submitting a form)                               |
| PUT          | Replaces all representations of target resources with the uploaded content       |
| DELETE       | Deletes all representations of target resources                                  |

There are much more HTTP requests like HEAD, CONNECT, OPTIONS, etc, but the 4 above should suffice for most use cases in networking.&#x20;

## HTTP Responses

Each HTTP request is followed by a response to the sender. In the case of a `GET` request to Wikipedia, this response would contain the page we were looking for. The response to a `POST` request would confirm that the site received the data properly (given that the request was correctly formatted and had the proper permission), and possibly contain other information.

HTTP requests are composed of two main parts: the **header** and the **body**. The header contains information about the request and request body. For example, it will specify the type of the request, timestamps from when it was sent, and (often) authorization tokens. The body contains the content of the request. This is usually where all of the data we want to send or receive will go.


# 6.2 3rd Party Libraries

Using 3rd party libraries (any library where the latest version of the code is not maintained and hosted by YOU) is a very wise move in Android Development. Many libraries have implemented tasks that would otherwise distract from your goal in the creation of an application.

### Example:&#x20;

If you wanted to make simple GET requests for something simple like a café menu: something that is received from the server once, and maybe a few more times incrementally with time, would it make sense to implement the entirety of the HTTP request protocol?&#x20;

**Absolutely not.**

This would be incredibly difficult as it require implementing the network socket and and interacting with the L2 layer of the network (to learn more take CS 4450 (or don't)).&#x20;

We want to be SMART coders and delegate our development interests and efforts accordingly. Using 3rd party libraries allow us to save time in the banal, uninteresting, mundane, or complicated code we otherwise would have to grind through making so use them!

For networking in Android, you'll almost always use a library. In the next section we'll introduce [OkHttp](https://square.github.io/okhttp/), which makes networking easy!

### More Libraries

As you grow as an Android developer, you'll come to find more and more libraries to suite your needs! Below are some more libraries for you to explore/use!

[Lottie](https://airbnb.design/lottie/), for easily add high-quality animation to any native app.

[Retrofit](http://square.github.io/retrofit), for turning your HTTP API into a Kotlin interface.

[Glide](https://github.com/bumptech/glide), an image loading library.

[ZXing](https://github.com/zxing/zxing), for adding barcode/QR-code scanning capabilities.


# 6.3 JSON and Moshi

## What is a JSON?

JSON, or **JavaScript Object Notation**, is a way of representing data. While its syntax comes from JavaScript, it is still language independent.

When it comes to networking, JSON is typically used in two cases. One is to POST data to a server. The other is a server’s response to the client. Servers may choose to use other formats, like XML, so don’t assume that every server will use JSON (but it is the most common type of format).

There are two main advantages when using JSON with networking. It's very easy for a client to parse the file. Also, it is easily formatted in a way that’s easy for us humans to read.

![Example JSON](https://195521982-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LvOeSx5ZqjJA8sxykMu%2Fuploads%2Fo9IwqzP04FBC7qJ6Ivro%2Fimage.png?alt=media\&token=733c5e98-8f01-411f-9f73-2ea972e07cef)

Above is a sample of JSON data (if you're familiar with python, it looks very similar to a dictionary).

The data consists of pairs of **keys** and **values**. There is a colon character separating each key and value. In the example, **base** is a key, and **GBP** is the corresponding value. A comma separates each pair.

A *value* can be a *string* in double quotes, or a *number*, or true or false or null, or another *object* or an *array*. These structures can be nested (e.g. I can have an array be a list of objects).

Information within curly braces is an **object**. In the example above, the value for the **rates** key is another object, containing more pairs.

![JSON Array](https://195521982-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LvOeSx5ZqjJA8sxykMu%2Fuploads%2FRVTfsMdUvJa5aqwEOKqa%2Fimage.png?alt=media\&token=9fdd1af4-22a8-4abf-b88d-5e62f17392c8)

Above is an example of a JSON array.

See how this starts and ends with square brackets? This indicates that the data is an array. Each object is a separate ‘record’ or element in the array. Each object in the array is also separated by a comma.&#x20;

{% hint style="info" %}
For the sake of example, the array is a list of objects but the array can be a list of any of the valid JSON types (you can also have arrays of many different types).
{% endhint %}

From this you can kind of see how tedious it would be to try and iterate through an array like this to parse the JSON objects (which is where [Moshi](https://github.com/square/moshi) comes into play to make this process easier for us)!

## Using Moshi

Many times what's returned from networking calls are class objects represented as JSONs, which can be a headache to try and parse manually (and often times isn't as efficient). In comes [**Moshi**](https://github.com/square/moshi), which is a handy library also developed by Square used for converting between JSON and Kotlin objects. In tandem they make networking in Android super easy!

### Setting up

In a language like python, we would need to `pip install x` and then later `import x` but in Kotlin we have a much more complicated file structure and system so we do the following (typically):

In the build.gradle we have to add dependencies to "install" the library

![The build.gradle we want is the second listed!](https://195521982-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LvOeSx5ZqjJA8sxykMu%2Fuploads%2FsVcfPQ7k0CnvPj4IVV4s%2Fimage.png?alt=media\&token=1348b1dd-47fa-4af6-a706-4cd6014a4bc7)

Add the following dependencies in your module-level build.gradle file:

```groovy
// Plugins should be located at the very top of your file
apply plugin: 'kotlin-kapt'

// Alternative if your plugins look like this!
plugins {
   ...
   id 'kotlin-kapt' 
}

...

dependencies {
   implementation("com.squareup.moshi:moshi-kotlin:1.13.0")
   kapt("com.squareup.moshi:moshi-kotlin-codegen:1.13.0")
}
```

We're gonna be referring to the classes below as we introduce Moshi:

```kotlin
@JsonClass(generateAdapter = true) // Data classes MUST have this line right above
data class BlackjackHand(
  val hidden_card: Card,
  val visible_cards: List<Card>,
  ...
)

@JsonClass(generateAdapter = true)
data class Card(
  val rank: Char,
  val suit: Suit
  ...
)

@Keep
enum class Suit {
  CLUBS, DIAMONDS, HEARTS, SPADES;
}
```

Moshi gives us JSONs like this that we can read from and write to:

```json
{
  "hidden_card": {
    "rank": "6",
    "suit": "SPADES"
  },
  "visible_cards": [
    {
      "rank": "4",
      "suit": "CLUBS"
    },
    {
      "rank": "A",
      "suit": "HEARTS"
    }
  ]
}
```

Pay special note that the names of our fields in our data class should correspond to what we expect to receive from our response. Check out[ this section](#custom-field-names-with-json) to see how we can change that!

### Converting JSON -> Kotlin

If we want to convert JSONs into objects of our Kotlin class, we can do something like what's below:

```kotlin
val json: String = ... // JSON from our response

val moshi: Moshi = Moshi.Builder().build()
val jsonAdapter: JsonAdapter<BlackjackHand> = 
    moshi.adapter<BlackjackHand>(BlackjackHand::class.java)

// fromJson(...) convers the JSON into a BlackjackHand
val blackjackHand: BlackjackHand = jsonAdapter.fromJson(json)
```

### Converting Kotlin -> JSON

If we want to go the other way around:

```kotlin
val blackjackHand = BlackjackHand(
    Card('6', SPADES),
    listOf(Card('4', CLUBS), Card('A', HEARTS))
  )

val moshi: Moshi = Moshi.Builder().build()
val jsonAdapter: JsonAdapter<BlackjackHand> = 
    moshi.adapter<BlackjackHand>(BlackjackHand::class.java)

// JSON that can be used in a POST request body, etc.
val json: String = jsonAdapter.toJson(blackjackHand)
```

Moshi has built-in support for reading and writing Kotlin's core data types:

* Primitives (int, float, char...)
* Arrays, Collections, Lists, Sets, and Maps
* Strings
* Enums

### Parse JSON Arrays

Say we have a JSON string of this structure:

```json
[
  {
    "rank": "4",
    "suit": "CLUBS"
  },
  {
    "rank": "A",
    "suit": "HEARTS"
  }
]
```

We can now use Moshi to parse the JSON string into a `List<Card>:`

```kotlin
val cardArrayJsonResponse: String = ...


// Creates a List<Card> type to adapt from
val type = Types.newParameterizedType(
    List::class.java, //List:class.java must come first!
    Card::class.java
)

val adapter = moshi.adapter<List<Card>>(type)
val cards: List<Card> = adapter.fromJson(cardsJsonResponse)
```

### Custom field names with @Json

Moshi works best when your JSON objects and Java or Kotlin classes have the same structure. But when they don't, Moshi has annotations to customize data binding.

Use `@Json` to specify how Kotlin properties map to JSON names. This is necessary when the JSON name contains spaces or other characters that aren’t permitted in Kotlin property names. For example, this JSON has a field name containing a space:

```json
{
  "username": "jesse",
  "lucky number": 32
}
```

We can't have spaces in our Kotlin property names, so we can create a mapping of how Moshi should convert ourselves:

```kotlin
data class Player(
  val username: String
  @Json(name = "lucky number") val luckyNumber: Int

  ...
)
```


# 6.4 Retrofit

**Retrofit** is a type-safe HTTP client for Android and Java that allows you to retrieve or upload data through a REST-based web service. Let's go through how you can set up Retrofit, integrate Moshi for JSON parsing, and make network requests that handle your API data like a pro!

### What is Retrofit?

Retrofit is a **type-safe library** that simplifies the process of making HTTP requests and converting responses. Instead of writing tons of boilerplate code for HTTP clients, Retrofit lets you **define your API routes** as Kotlin interfaces and converts them into functions. You can describe each route using annotations like `@GET`, `@POST`, `@PUT`, and `@DELETE`.

For example, let’s say you want to retrieve data from a RESTful API. Using Retrofit, you’ll create an interface that defines API routes as functions, specifying the type of request (GET, POST, etc.) and any parameters required. Once this interface is ready, you can call these functions as if they were native Kotlin functions.

### Why Use Moshi?

When your app communicates with APIs, it will often receive data in **JSON** format. Instead of manually converting JSON to Kotlin objects, Moshi does this for you. As we discussed in the previous section Moshi allows for the automatic interpretation of JSON objects into Kotlin data classes, making it much easier to handle incoming and outgoing data from the API.

### API Documentation: The Starting Point

Before diving into the code, ensure you have the **API documentation** from your web service. This will tell you:

* How to call certain API requests.
* What parameters are required.
* What the API returns.

You’ll use this information to model your data and define your Retrofit interfaces.

### Setting Up Retrofit and Moshi in Your Project

First, you'll need to add the necessary dependencies to your project. Open your `build.gradle.kts` file and include the following lines:

```kotlin
dependencies {
    implementation("com.squareup.retrofit2:retrofit:2.9.0")
    implementation("com.squareup.moshi:moshi-kotlin:1.14.0")
    ksp("com.squareup.moshi:moshi-kotlin-codegen:1.15.1")
    implementation("com.squareup.retrofit2:converter-moshi:2.9.0")
}

plugins {
    id("com.google.devtools.ksp").version("2.0.0-1.0.24")
}
```

Additionally, don't forget to add internet permissions to your `AndroidManifest.xml` file:

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

### Step 1: Model Your Data with Kotlin Data Classes

You’ll need to create data models that represent the structure of the JSON data you’re working with. This is where Moshi comes into play. Use `@JsonClass` and `@Json` annotations to map the JSON fields to your Kotlin data class fields.

Here’s an example of how you can model a simple user data object:

```kotlin
data class User(
    @Json(name = "id") val userId: Int,
    @Json(name = "name") val userName: String,
    @Json(name = "email") val userEmail: String
)
```

### Step 2: Define the API Interface

Next, define your API routes by creating an **interface**. Use annotations like `@GET`, `@POST`, `@PUT`, etc., to specify the HTTP method for each endpoint. You can also include headers, query parameters, or path variables with special annotations.

Here’s how you can define an API interface to fetch user data:

```kotlin
interface UserService {
    @GET("users/{id}")
    @Headers("API_KEY: {your_api_key}")
    suspend fun getUser(
        @Path("id") userId: Int,
    ): User
}
```

### Step 3: Create a Retrofit Instance in a Singleton

You should create a **singleton** Retrofit instance to make network requests throughout your app. You’ll configure Retrofit to use the Moshi converter for JSON handling.

```kotlin
object RetrofitInstance {
    
    private const val BASE_URL = "https://api.example.com/"

    private val retrofit by lazy {
        Retrofit.Builder()
            .baseUrl(BASE_URL)
            .addConverterFactory(MoshiConverterFactory.create())
            .build()
    }

    val api: UserService = retrofit.create(UserService::class.java)
    
}
```

This singleton creates a `Retrofit` instance with the Moshi converter and provides access to the API interface `UserService`.

### Step 4: Making Network Requests

Now that everything is set up, making a network request is as simple as calling the interface function. Since you’re likely to use **coroutines** for asynchronous calls, don’t forget to run your network request in a coroutine context.

```kotlin
val scope = rememberCoroutineScope()
fun fetchUser(userId: Int, authToken: String) {
    scope.launch {
        withContext(Dispatchers.IO){
            try {
                val user = RetrofitInstance.api.getUser(userId, authToken)
                println("User fetched: $user")
            } catch (e: Exception) {
                println("Error fetching user: ${e.message}")
            }
        }
    }
}
```

* We use `CoroutineScope` with `Dispatchers.IO` to run the network request on a background thread.
* The `getUser` function is called, fetching the user data based on the provided `userId` and `authToken`.
* If the request is successful, the user data is printed. Otherwise, an error message is shown.

By using Retrofit with Moshi, you can make your network code cleaner, more readable, and easy to maintain. No more manually parsing JSON or writing complex HTTP clients from scratch—Retrofit and Moshi handle all of that for you. Whether you're building a small app or working on a large project, this setup simplifies making reliable API calls in Kotlin.


# 6.5 Summary

From TCP/UDP to IPv4/IPv6, there is an incredible amount of minutia that may affect the development of your app that we are unable to cover in a lesson under a textbook in length. However, there doesn’t exist a single widely used app that doesn’t require networking of some kind. This lesson just scratches the surface of how networking allows your app to coordinate with servers, but it will have been enough to get you started.&#x20;


# 7. MVVM Design Pattern

The approach to state management is an extremely important question when it comes to designing a scalable frontend architecture. Using MVVM will help you create maintainable Android apps with minimal boiler plate. It is a powerful way to keep your code clean and organized.&#x20;


# 7.1 Key Idea

MVVM stands for model-view-viewmodel, let's go over each of these and see how they provide a framework for state management.

<figure><img src="https://195521982-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LvOeSx5ZqjJA8sxykMu%2Fuploads%2FBZy9AzFrnnTkLaLa7JA3%2Fmvvm.png?alt=media&amp;token=e66027ad-9685-40ee-8975-b6aff1bc5824" alt=""><figcaption><p>Keep this visual in mind while reading!</p></figcaption></figure>

## View

The view is what you are already most familiar with, it consists of the composables that the user sees as they explore the app. Here's an example of a `CommentView` composable:

```kotlin
@Composable
fun CommentView(
    username: String,
    commentText: String
) {
    Column(modifier = Modifier.padding(8.dp)) {
        Text(
            text = username,
            fontWeight = FontWeight.Bold,
            fontSize = 16.sp,
            modifier = Modifier.padding(bottom = 4.dp)
        )
        BasicText(text = commentText)
    }
}
```

Notice that `CommentView` contains only logic for displaying the `username` and `commentText`. This is an important detail that generalizes to the concept of *views* as a whole: views should not store any business logic. Ideally, the view's only responsibility should be to properly display a *state*. In this case, the *state* of `CommentView` is the `username` text and `commentText`. So then where should the business logic go? Read on to discover :star\_struck:

## ViewModel

The main purpose of a ViewModel from a coding standpoint store the state of a screen such that it survives *configuration changes*, such as the user rotating their screen or changing their screen size (like splitting to multiple windows with a folding phone). But from an organization standpoint, the ViewModel takes a larger role than just the state holder. The ViewModel is also responsible for storing the "business logic", or intermediary logic that we use to process data between the View and the Model. A common example of business logic is password validation. If we want to make sure a password is secure enough before sending it off to the Model to create the user's account, this validation logic should be stored in the ViewModel.&#x20;

So to reiterate, a ViewModel has two main functions:

1. Retrieve data from the Model, and nicely package it into a digestible state for the View
2. Process / validate user input from the *view* and send it to the Model.&#x20;

Having our code separate in this way makes testing and maintenance far easier. If you need to change the business logic, then you shouldn't need to touch the UI at all. If you want to change how the UI looks, you shouldn't need to change any business logic to accommodate that. This also allows for easier testing. We can test our business logic in a completely separate environment from our UI.&#x20;

The ViewModel can send notifications to the UI, also referred to as *effects*. For example, if the UI requests the ViewModel to validate a credit card number and the validation fails, the ViewModel can trigger an effect to show a snackbar indicating the number is invalid.

## Model

The model acts as a bridge between the ViewModel and the actual database that we use. The model abstracts away common data operations that the ViewModel might use. This way, if the database changes in the future, we only have to change these abstractions without needing to change the business logic.&#x20;

### The value of the Model abstraction

Here's an example of how a model could be valuable: let's say that we have a fitness app and we want to show the user a monthly summary of their workout statistics. At this fitness app company, the backend team doesn't have the bandwidth to create a custom summary endpoint just for your use case, so they expect you to assemble the data from other endpoints.

You create two functions in your model: `getMonthlySteps` and `getMonthlyCalories`. You use these in your ViewModel to nicely package the data into a summary `data class` for your View, and all is well.

Later down the road, the backend team decides it's too costly for you to make two API calls to generate one summary item, so they decide to give you a custom summary endpoint that contains both of these data points. Then because of your great architectural decisions, all you have to do is change the *implementation* of `getMonthlySteps` and `getMonthlyCalories`. The method spec can stay the same, so their use in the ViewModel can stay the same too. To reimplement this to only make one API call, you might cache the resulting summary item from `getMonthlySteps` and have `getMonthlyCalories` read from the cache, or vice versa.&#x20;

### Events

Just as your ViewModel can send notifications to your View, you may want your Model to send notifications to your ViewModel. For example, if the Model abstracts away a long-running database operation from the ViewModel, and this operation fails, we may want to notify the ViewModel that this is the case. With this notification, the ViewModel could update the state of the View, or even send an effect to the View alerting it.&#x20;

## Unidirectional data flow&#x20;

You may have noticed a pattern throughout this chapter: state flows down, and events flow up.&#x20;

The Model holds the actual data that the view needs for its state, this data flows down to the ViewModel. The ViewModel aggregates and packages this data into a nice readable form for the View, and then sends this state down to the view. &#x20;

The view sends events to the ViewModel, for example the user pressing a button. The ViewModel then processes this event and sends it to the Model, which then processes the event and sends it to the database.&#x20;

This is a nice pattern to be aware of, since being mindful of it when creating an app provides benefits that help with scalability and testability. The idea even extends beyond Android development, as you'll see it used in other frontend frameworks. I highly recommend reading the section on [unidirectional data flow](https://developer.android.com/develop/ui/compose/architecture#udf) from the Android documentation. It explains the concept much better than I can and provides great guidance on how to follow the principle.


# 7.2 Implementation Ideas

{% hint style="info" %}
Note: MVVM has many different approaches to its implementation. I will show some of the ways that this pattern can be implemented, but keep in mind that there is no one right approach.&#x20;
{% endhint %}

## View

The View is pretty straightforward, you're just creating composables to display the information that you need to. In this section I'll just provide some general tips and best practices for creating your View in MVVM:

* Always pass the minimum amount of information to your view
  * This tip helps keep our code debuggable. Let's say we need to navigate from Screen A to Screen B. We have two options. We could pass the entire `navController` down to Screen A, or we could pass a function, `navigateToScreenB: () -> Unit` to Screen A. If we pass the entire `navController`, we are also giving Screen A access to many other methods that it could use to influence the screen we are on and our navigation stack. Let's say in the future we have an issue with navigation in our app. If we only passed `navigateToScreenB` function to Screen A, then we would instantly have the guarantee that Screen A only navigates to screen B, and we would have to do less debugging on that screen, as opposed to passing the entire `navController`.
  * A corollary to this tip is that ViewModels should never be passed into your Composables. They can be [*injected*](https://developer.android.com/training/dependency-injection/hilt-android), but should never be passed down directly as a parameter.&#x20;
* Try to minimize business logic in the view
  * Let's say you're working on Eatery, and you want to determine what the title should show on the home screen. Suppose that if there is more than 1 eatery open, we want to show the text "UAW is not on strike!" is our app title, and if there are no dining halls open, we want to show the text "The strike is still ongoing 😔". It could be tempting to pass the list of Eateries directly to the composable, and have the composable determine which text to show. But it is more appropriate to pass a `screenTitle: String`  down to the view. This keeps business logic out of the view and allows you to easily write tests for your ViewModel to determine what your screen displays in what conditions.

## ViewModel

I gave an example of a fairly standard ViewModel in my [guide on Flows](https://android-course.cornellappdev.com/chapters/flows#example-1-account-creation-validation) that shows how to incorporate Flows into a ViewModel. I recommend taking a look at that one first. Here I'll give an example of another common ViewModel use case: loading an asynchronous resource.

{% code title="HomeViewModel.kt" %}

```kotlin
sealed class HomeViewState {
    data class Loaded(
        val pictures: List<PictureViewState> = emptyList(),
    ) : HomeViewState()

    object Loading : HomeViewState()
    data class Error(val message: String) : HomeViewState()
}

class HomeViewModel(
    private val pictureRepository: PictureRepository = PictureRepositoryProvider.getInstance(),
    private val pictureService: PictureService = PictureServiceProvider.getInstance()
) : ViewModel() {
    private var page = PictureService.START_PAGE
    private val _homeViewState = MutableStateFlow<HomeViewState>(HomeViewState.Loading)
    val homeViewState = _homeViewState.asStateFlow()

    init {
        observePictureRepository()
        loadNextPage()
    }

    private fun observePictureRepository() {
        pictureRepository.observeAll().map { pictures ->
            _homeViewState.update {
                HomeViewState.Loaded(
                    pictures = pictures.map { PictureViewState(it) }
                )
            }
        }.launchIn(viewModelScope)
    }

    fun loadNextPage() = viewModelScope.launch {
        try {
            val pictures = pictureService.pictures(page)
            pictureRepository.put(pictures)
            page++
        } catch (e: Exception) {
            _homeViewState.update {
                HomeViewState.Error(e.localizedMessage ?: "Something went wrong.")
            }
        }
    }
}
```

{% endcode %}

There's a lot going on in this example that shows common practices with ViewModels, so let's break this analysis down:

### ViewState

For true best practices, each ViewModel should have a corresponding ViewState `data class`. Sometimes this practice can be a bit overkill, especially if your ViewModel only exposes a couple of fields to the View. But generally this idea helps keep things managable as your code scales. Some reasons that this approach tends to be a good idea:

1. It encourages a clear separation of concerns between the ViewModel and the View. The purpose of the ViewModel is to expose the UI state to the View, and the ViewModel should provide exactly the information that the View needs to the View.&#x20;
2. It simplifies state management by encapsulating all relevant UI data in a single object, making it easier to observe, test, and update the UI.
3. Compose relies on state-driven UI rendering. A dedicated ViewState ensures UI recomposes automatically when state changes, aligning with Compose’s reactive nature.

### Interactions With the Model

We can see that the ViewModel has two main purposes for interacting with the model:

* Packaging the information from the model into a readable ViewState
  * The function `observePictureRepository` is responsible for this. We read whatever the latest data is from the model, and then update the UI state accordingly.
* Allowing UI events to flow from the View to the Model.
  * This is what the `loadNextPage` function is for. When the UI wants us to load a new page of pictures, it indicates that by calling the `loadNextPage` function. This function calls upon the `pictureService` to get more pictures from the API, and then it indicates this change to the `pictureRepository` , by providing it with the latest picture data through its `put` method.

## Model

Remember there is no single agreed upon implementation of what a Model needs to look like, so I'm going to share a couple of the main variations I've worked with throughout my time as an Android developer.

### Model as a Cache

I call this section "Model as a Cache" because the model more just functions as a data structure, and doesn't have much special functionality. [Room](https://developer.android.com/training/data-storage/room) and [DataStore](https://developer.android.com/topic/libraries/architecture/datastore) can be thought of as implementations of this approach as well, but you might want to make a Model that wraps these for further abstraction. Let's take a look at an example of building a data cache from scratch:

{% code title="PictureRepository.kt" %}

```kotlin
typealias PictureId = String

class PictureRepository {
    private val idsToPictures = MutableStateFlow(mapOf<PictureId, Picture>())

    fun observe(pictureIds: Set<PictureId>): Flow<List<Picture>> =
        idsToPictures.map { idsToPictures ->
            pictureIds.mapNotNull { idsToPictures[it] }
        }.distinctUntilChanged()

    fun observeAll(): Flow<List<Picture>> =
        idsToPictures.map { it.values.toList() }.distinctUntilChanged()

    fun put(pictures: List<Picture>) {
        idsToPictures.update {
            it + pictures.associateBy { pic -> pic.id }
        }
    }
}
```

{% endcode %}

We can see that this `PictureRepository` simply acts as a data structure, almost like a regular Map. Except we use `Flow` so users of the repository can observe updates to the data live as they occur. We expose a `put` that updates the `idsToPicture` Flow, so then any observers using either `observe` or `observeAll` will be updated properly.&#x20;

A more complicated repository might expose multiple Flows, with multiple respective `put` and `observe` methods, and then it could be the responsibility of the ViewModel to aggregate the data into what the UI wants to display.&#x20;

### Model as a Data Source

Let's consider a data source as a more abstract notion of a cache. So the ViewModel should be able to call upon this data source and get the data that it needs. It doesn't need to worry about any of the implementation details or where the data source gets its data (as opposed to the previous example, where the ViewModel places data in the model). Let's use a trimmed version of Eatery Blue's `EateryRepository` as an example:

```kotlin
@Singleton
class EateryRepository @Inject constructor(private val networkApi: NetworkApi) { 
    private val _eateryFlow: MutableStateFlow<EateryApiResponse<List<Eatery>>> =
        MutableStateFlow(EateryApiResponse.Pending)

    /**
     * A [StateFlow] emitting [EateryApiResponse]s for lists of ALL eateries, if loaded successfully.
     */
    val eateryFlow = _eateryFlow.asStateFlow()
    
    /**
     * A map from eatery ids to the states representing their API loading calls.
     */
    private val eateryApiCache: MutableMap<Int, MutableStateFlow<EateryApiResponse<Eatery>>> =
        mutableMapOf()
    
    
    /**
     * Makes a new call to backend for the specified eatery. After calling,
     * `eateryApiCache[eateryId]` is guaranteed to contain a state actively loading that eatery's
     * data.
     */
    private fun pingEatery(eateryId: Int) {
        // If first time calling, make new state.
        if (eateryApiCache[eateryId] == null) {
            eateryApiCache[eateryId] = MutableStateFlow(EateryApiResponse.Pending)
        }

        eateryApiCache[eateryId]!!.value = EateryApiResponse.Pending

        CoroutineScope(Dispatchers.IO).launch {
            try {
                val eatery = getEatery(eateryId = eateryId)
                eateryApiCache[eateryId]!!.value = EateryApiResponse.Success(eatery)
            } catch (_: Exception) {
                eateryApiCache[eateryId]!!.value = EateryApiResponse.Error
            }
        }
    }
    
    /**
     * Returns the [StateFlow] representing the API call for the specified eatery.
     * If ALL eateries are already loaded, then this simply instantly returns that.
     */
    fun getEateryFlow(eateryId: Int): StateFlow<EateryApiResponse<Eatery>> {
        if (eateryFlow.value is EateryApiResponse.Success) {
            return MutableStateFlow(
                EateryApiResponse.Success(
                    (eateryFlow.value as EateryApiResponse.Success<List<Eatery>>)
                        .data.find { it.id == eateryId }!!
                )
            )
        }

        // If not called yet or is in an error, re-ping.
        if (!eateryApiCache.contains(eateryId)
            || eateryApiCache[eateryId]!!.value is EateryApiResponse.Error
        ) {
            pingEatery(eateryId = eateryId)
        }

        return eateryApiCache[eateryId]!!
    }
}
```

We can see that this model has a cache nested as a part of the model. The model is now responsible for fetching all of its data, and this part has been abstracted away from the ViewModel.  The model could also expose methods for modifying its data. Although this works great for a simple example such as read-only Eatery details, sometimes it can become complicated to have one file that calls the API and manages the ViewState in one.&#x20;

We also have to try and find a delicate balance between the responsibilities of the Model and the ViewModel.  For example, this `EateryRepository` already packages the data in a `MutableStateFlow<EateryApiResponse<List<Eatery>>>`. If I wanted a ViewModel for the screen that shows all eateries, then I would basically just be exposing this state flow, and not much else. This makes the Model feel more like a ViewModel, since it's already doing the work of aggregating the data into this more readable form of `EateryApiResponse`

In the real Eatery codebase, the ViewModel combines the `userPreferencesRepository` and the `eateryRepository` to aggregate the data and provide the View with information about what eateries are favorited. This gives a nice purpose for the ViewModel. But the point is that we never want to have a single Model have too many responsibilities, to the point where its corresponding ViewModel just becomes boilerplate.&#x20;


# 8. Flows

In Kotlin, a `Flow` is an asynchronous stream of values. Values will flow down the stream, and there are collectors that can observe these values. `Flow` is probably best explained by example, so let's go over some common use-cases for `Flow`s in Android Development

## Example 0: Pure flow example

```kotlin
class NewsRemoteDataSource(
    private val newsApi: NewsApi,
    private val refreshIntervalMs: Long = 5000
) {
    val latestNews: Flow<List<ArticleHeadline>> = flow {
        while(true) {
            val latestNews = newsApi.fetchLatestNews()
            emit(latestNews) // Emits the result of the request to the flow
            delay(refreshIntervalMs) // Suspends the coroutine for some time
        }
    }
}
```

Here we have a very pure example of how a `Flow` can be used. I took this from [creating a flow](https://developer.android.com/kotlin/flow#create) on the Android developer documentation since it is a very nice first flow usage example. Here we use `flow` , which is a flow builder that allows us to run `suspend` functions inside and `emit` their results to the flow. We could observe this flow using `latestNews.collect` , so then whenever the `emit` function is called, the lambda we pass in to `latestNews.collect` is also called. Let's see how we could observe this `Flow` in the UI.&#x20;

```kotlin
val latestNewsState: State<List<String>> =
            newsRemoteDataSource.latestNews.collectAsState(initial = listOf())

        Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
            LazyColumn {
                items(latestNewsState.value) {
                    Text(text = it)
                }
            }
        }
```

We can use the `collectAsState` method. `collect` is a method on a Flow that suspends the code block until the `FlowCollector` finishes emitting values. In this case, our `FlowCollector` is the `while(true)` block from the first code snippet, so `collect` block suspends indefinitely. However, `collectAsState` runs `collect` in a coroutine and transforms the results into a `State<List<String>>`, and as we know whenever the `State` value changes, our UI recomposes and automatically updates. This is one of the benefits of reactive UI. So even though running a lambda on each flow value emission is a more of an imperative idea, we can still use it in a reactive (declarative) context.

## Example 1: Account creation validation

&#x20;Let's say that the user is typing in their account information, and we want the UI to update *live* based on whether their information is valid. This could be if their email is valid, if their password is secure enough, etc.. But for this demo, we're going to do a basic version with just username and password. Let's walk through the ViewModel code to see how it uses `Flow` to streamline this idea.&#x20;

{% code title="MainScreenViewModel.kt" %}

```kotlin
data class UiState(
    val username: String = "",
    val password: String = "",
    val isValid: Boolean = false,
)

@HiltViewModel
class MainScreenViewModel @Inject constructor(
) : ViewModel() {
    private val password = MutableStateFlow("")
    private val username = MutableStateFlow("")

    private val _uiState = MutableStateFlow(UiState())
    val uiState = _uiState.asStateFlow()

    init {
        combine(username, password) { username, password ->
            val isValid = username.isNotBlank() && password.length > 8
            _uiState.update {
                UiState(
                    username,
                    password,
                    isValid,
                )
            }
        }.launchIn(viewModelScope)
    }

    fun updatePassword(newPassword: String) {
        password.update { newPassword }
    }

    fun updateUsername(newUsername: String) {
        username.update { newUsername }
    }
}
```

{% endcode %}

Let's walk through this code. You'll first notice that `password` and `username` are `MutableStateFlow`s. A `MutableStateFlow` is a type of `Flow` that holds a state, and it emits new values whenever its state is updated. You may be wondering, *why not just use a state to represent these values*? The main reason is that we want to be able to launch an operation whenever either of these values updates. States are automatically observed by the UI, since whenever a state's value changes the UI recomposes. But we don't have a good way of observing state updates ourselves. `Flow` emissions however are easily observed through the `combine` function. Whenever the `username` or `password` flows have an emission, the `combine` function is called, and we update the `UiState` accordingly.&#x20;

So we use `combine` to create a new `Flow` that is a result of applying the transform functions to the values from the emissions of `username` and `password`. The thing is, the `combine` method alone just initializes this new `Flow`, but it doesn't actually `collect` it, so the transform lambda we wrote won't be called. That's why we use `launchIn`, which `collect`s the flow in a certain `CoroutineScope`. We use `viewModelScope` , so when our ViewModel gets disposed, our coroutine will get disposed with it. and we don't have a memory leak where we are using resources to constantly observe this `Flow`.

Some other design decisions for this ViewModel include making all the `MutableStateFlow`s private. This was done for separation of concerns, so we don't have to worry about the UI updating our ViewModel flows however they want to. The only functions that mutate the state that we expose to the UI are `updatePassword` and `updateUsername` , so now we know exactly where updates to this ViewModel's state will be coming from: usages of those functions. We expose the uiState with `asStateFlow`, which makes it read-only.&#x20;


# 9. The Art and Ontology of Software

Adam's Fall 2020 Guest Lecture

Check out the video [here](https://www.youtube.com/watch?v=d-X94xlUrEI).&#x20;

If you want to learn more about this, see these book recommendations:

| Title                                                      | Author             |
| ---------------------------------------------------------- | ------------------ |
| The Art of Computer Programming                            | Donald Knuth       |
| <p>Gödel, Escher, Bach: an Eternal Golden Braid</p><p></p> | Douglas Hofstadter |
| Simulacra and Simulation                                   | Jean Baudrillard   |


# 10. 🔥 Firebase

Chris' Spring 2021 Guest Lecture

Firebase is a platform developed by Google to “build, improve, and grow your app”, and the tools it gives you cover a large portion of the services that developers would normally have to build themselves, but don’t really want to build, because they’d rather be focusing on the app experience itself. This includes things like analytics, authentication, databases, configuration, file storage, push messaging, and the list goes on (it's pretty extensive). The services are hosted in the cloud, and scale with little to no effort on the part of the developer.

<https://firebase.google.com/>

### Topics

* Setting up Firebase
* Authentication
* Analytics
* Messaging
* Firestore


# 10.1 Setting up Firebase

Setting up Firebase is very easy and pain free (kinda), just follow these steps! ✨✨

### Creating a Firebase Project in the Console

&#x20;To begin, sign in to the [Firebase console](https://console.firebase.google.com). Make sure you use the Google account you want to tie to your project! You should be prompted with a project creation button (or a button to add a project if you've used Firebase before)

![Landing screen for Firebase console](https://195521982-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LvOeSx5ZqjJA8sxykMu%2F-MYbjKLUwrrYyatuEhK0%2F-MYbkHp43nS7FHbUwyXY%2Fimage.png?alt=media\&token=7317fd97-8c47-478e-a204-ff923837cc18)

![Create a project screen](https://195521982-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LvOeSx5ZqjJA8sxykMu%2F-MYbjKLUwrrYyatuEhK0%2F-MYblIgDESglOkBkq-US%2Fimage.png?alt=media\&token=9a4ef42d-2fc9-4345-992f-7e90eac76354)

### Registering the App With Firebase

Once on the project home page after creating a Firebase project, you’ll notice several options and details. Here’s where you’ll find the option to add Firebase to an app (*hint: click the Android symbol* 😊).

![Project home landing screen](https://195521982-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LvOeSx5ZqjJA8sxykMu%2F-MYbjKLUwrrYyatuEhK0%2F-MYbn7pKdz9nUwr1_qCn%2FScreenshot%202021-04-18%20214057.png?alt=media\&token=6cc235d6-4732-46cd-8d10-73b013da80de)

You’ll see a screen requesting data from your app.

![Add Firebase to your Android app](https://195521982-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LvOeSx5ZqjJA8sxykMu%2F-MYbjKLUwrrYyatuEhK0%2F-MYbmzV3AV_64kRVrequ%2Fimage.png?alt=media\&token=e61eb273-5623-4b86-b9c6-5282e5692c25)

Enter the following information:

* Android Package Name&#x20;
* App Nickname
* Debug signing certificate SHA-1

The Android Package name should be the applicationId in your module level *build.gradle* file.&#x20;

\
The Debug signing certificate SHA-1 key, while optional, is necessary for some services in Firebase (ie certain authentication features and dynamic linking). The SHA-1 key can be retrieved by opening the terminal in Android Studio in your project and running&#x20;

```
gradlew signingReport
```

![Terminal tab highlighted](https://195521982-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LvOeSx5ZqjJA8sxykMu%2F-MYbjKLUwrrYyatuEhK0%2F-MYbp9xwxM7P_o_6OKxf%2Fimage.png?alt=media\&token=672d675e-1aed-4295-8260-0d0a8a3b2221)

### Adding Firebase Configuration Files to Your Android Project

After registering an app, the next screen prompts you to download a JSON file that contains the service configuration for Firebase. Save it anywhere on your computer and remember where you place it. You’ll add it to the project next.

![Landing page for downloading config file](https://195521982-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LvOeSx5ZqjJA8sxykMu%2F-MYbjKLUwrrYyatuEhK0%2F-MYbs5vjkZAIUisb_CYP%2Fimage.png?alt=media\&token=458d3294-c195-4558-bdce-e27241eec883)

&#x20;Go into Android Studio and switch to the *project view* in the left panel:

![](https://195521982-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LvOeSx5ZqjJA8sxykMu%2F-MYbjKLUwrrYyatuEhK0%2F-MYbstt_zmKcduOM_wK9%2Fimage.png?alt=media\&token=193a1e8a-cd8f-48fb-b4f8-dd7bd0716f9b)

Add the config file directly under \[project name]/app&#x20;

Next, we have to add corresponding dependencies. Switch back to the *Android view* in the same left panel:

![](https://195521982-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LvOeSx5ZqjJA8sxykMu%2F-MYbjKLUwrrYyatuEhK0%2F-MYbuAdKYWXG7Jn0dSQH%2Fimage.png?alt=media\&token=3ed50101-987f-465b-b8e4-c4a657bf583d)

&#x20;Open *build.gradle*, which Android Studio tags as *(Project: \[project name])*. This file is in *Gradle Scripts/build.gradle*.

Next, add this code in the dependencies section:

```
classpath 'com.google.gms:google-services:4.3.3'
```

Then open *Build.gradle*, labeled as *(Module: \[project name])*. This file is also in the *Gradle Scripts* section within the Android view.

Next, add this code under your plugins:

```
id 'com.google.gms.google-services'
```

![](https://195521982-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LvOeSx5ZqjJA8sxykMu%2F-MYbjKLUwrrYyatuEhK0%2F-MYbujstY0M1K2ggZwNK%2Fimage.png?alt=media\&token=d0b4c01f-03b5-4822-b448-07df5a38b556)

Next, add the following under your dependencies in the same gradle file:

```
implementation platform('com.google.firebase:firebase-bom:27.0.0')
implementation 'com.google.firebase:firebase-analytics-ktx'
```

Finally sync!

**`Now celebrate as you've just finished setting up Firebase and the true fun can start ✨✨`**


# 10.2 Authentication

Firebase Authentication provides backend services, easy-to-use SDKs, and ready-made UI libraries to authenticate users to your app. It supports authentication using passwords, phone numbers, popular federated identity providers like Google, Facebook and Twitter, and more

<https://firebase.google.com/docs/auth>

## Setting up Authentication

Make sure your Firebase project is set up, the steps of which are covered in the first section 11.1!

Add these two dependencies to your module-level Gradle file:

```
implementation 'com.google.firebase:firebase-auth-ktx'
implementation 'com.google.android.gms:play-services-auth:19.0.0'
```

These dependencies add the Firebase Authentication Android library and Google Play services SDK to your app. &#x20;

When accessing most Authentication services, you need the appropriate FirebaseAuth object.&#x20;

First, declare your auth variable:

```
private lateinit var auth: FirebaseAuth
```

In the onCreate/onCreateView method of your activity/fragment, initialize the auth variable as so:

```
auth = Firebase.auth
```

## General

### Check for signed-in users

When initializing your Activity, check to see if the user is currently signed in in your onStart method (which is called automatically after onCreate):&#x20;

```
override fun onStart() {
    super.onStart()
    val currentUser = auth.currentUser
    if (currentUser == null) {
        /**
         * No user is currently signed in, can update UI elements to account for this
         * or maybe redirect to some sign in activity!
         */
    } else {
        /**
         * A signed in user is detected, carry out some respective actions if needed!
         */
    }
}
```

{% hint style="info" %}
Check out [Manage Users](https://firebase.google.com/docs/auth/android/manage-users) to see operations one can do on a user object!
{% endhint %}

### Sign-out

You can sign out a user using:

```
Firebase.auth.signOut()
```

## Google Sign-In&#x20;

This will most likely just be a collection of the links below, some personal reflections also!

{% embed url="<https://firebase.google.com/docs/auth/android/google-signin#kotlin+ktx>" %}

{% embed url="<https://developers.google.com/identity/sign-in/android/sign-in>" %}

1\. Enable Google Sign-In in the Firebase console:\
&#x20;    1.1. In the [Firebase console](https://console.firebase.google.com/), open the **Auth** section for your project\
&#x20;    1.2. On the **Sign in method** tab, enable the **Google** sign-in method and click **Save**.

![Authentication section in the console](https://195521982-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LvOeSx5ZqjJA8sxykMu%2F-MYcOknt8AWV_Djv6f1c%2F-MYcPP8nreY6Bxv8OLxU%2Fimage.png?alt=media\&token=91a9d935-d6aa-4e40-9d55-0e8ed9f0366e)

2\. In your sign-in activity's `onCreate` method, configure Google Sign-In to request the user data required by your app. For example, to configure Google Sign-In to request users' ID and basic profile information, create a [`GoogleSignInOptions`](https://developers.google.com/android/reference/com/google/android/gms/auth/api/signin/GoogleSignInOptions.Builder#GoogleSignInOptions.Builder\(\)) object with the `DEFAULT_SIGN_IN` parameter. To request users' email addresses as well, create the `GoogleSignInOptions` object with the `requestEmail` option.

```
private lateinit var googleSignInClient: GoogleSignInClient

......

// Configure sign-in to request the user's ID, email address, and basic
// profile. ID and basic profile are included in DEFAULT_SIGN_IN.
val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
        .requestIdToken(getString(R.string.default_web_client_id))
        .requestEmail()
        .build()

googleSignInClient = GoogleSignIn.getClient(this, gso)
```

If you need to request additional scopes to access Google APIs, specify them with [`requestScopes`](https://developers.google.com/android/reference/com/google/android/gms/auth/api/signin/GoogleSignInOptions.Builder#requestScopes\(com.google.android.gms.common.api.Scope,%20com.google.android.gms.common.api.Scope...\)). For the best user experience, on sign-in, only request the scopes that are required for your app to minimally function. Request any additional scopes only when you need them, so that your users see the consent screen in the context of an action they performed. See [Requesting Additional Scopes](https://developers.google.com/identity/sign-in/android/additional-scopes).

3\. Add an ActivityResultLauncher for the Google Sign In Activity intent (the API launches a separate activity to sign the user in):

```
    private lateinit var intentLauncher: ActivityResultLauncher<Intent>
    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        intentLauncher =
            registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
                ...
            }
        ...
    }
```

4\. You can add a handy Google sign-in button via:

```
<com.google.android.gms.common.SignInButton
 android:id="@+id/sign_in_button"
 android:layout_width="wrap_content"
 android:layout_height="wrap_content" />
 
 <!--  Can add app:buttonSize attribute and specifiy icon_only, standard, or wide-->
 <!--  Can add app:colorScheme attribute and specifiy dark, light, or auto-->

```

5\. You can then register the OnClickListener for this button in your activity via:

```
findViewById<SignInButton>(R.id.sign_in_button).setOnClickListener(this)

```

6\.  In the activity's `onClick` method, handle sign-in button taps by creating a sign-in intent with the [`getSignInIntent`](https://developers.google.com/android/reference/com/google/android/gms/auth/api/signin/GoogleSignInClient#getSignInIntent\(\)) method, and starting the intent with the `ActivityResultLauncher`.

```kotlin
override fun onClick(view: View?) {
    if (view != null) {
        when (view.id) {
            R.id.sign_in_button -> {
                signInGoogle();
            }
        // ...
        }
    }
}
```

```kotlin
private fun signInGoogle() {
    val signInIntent = googleSignInClient.signInIntent
    intentLauncher.launch(signInIntent)
}
```

7\. **Expand the intent launcher**

```kotlin
private lateinit var intentLauncher: ActivityResultLauncher<Intent>
override fun onCreate(savedInstanceState: Bundle?) {
    ...
    intentLauncher =
        registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
            if (result.resultCode == Activity.RESULT_OK) {
                val task = GoogleSignIn.getSignedInAccountFromIntent(result.data)
                try {
                    // Google Sign In was successful, authenticate with Firebase
                    val account = task.getResult(ApiException::class.java)!!
                    firebaseAuthWithGoogle(account.idToken!!)
                } catch (e: ApiException) {
                    // Google Sign In failed, update UI appropriately
                }
            }
        }
    ...
}
```

8\.  After a user successfully signs in, get an ID token from the `GoogleSignInAccount` object, exchange it for a Firebase credential, and authenticate with Firebase using the Firebase credential:

```kotlin
    private fun firebaseAuthWithGoogle(idToken: String) {
        val credential = GoogleAuthProvider.getCredential(idToken, null)
        auth.signInWithCredential(credential)
            .addOnCompleteListener(this) { task ->
                if (task.isSuccessful) {
                    /**
                     * A signed in user is detected, carry out some respective actions if needed!
                     */
                    val user = auth.currentUser
                    ...
                } else {
                    /**
                     * No user is currently signed in, can update UI elements to account for this
                     * and display some feedback to the user.
                     */
                }
            }
    }
```

9\. All done :)

{% hint style="info" %}
Many of the other providers (i.e. Facebook and Twitter and GitHub) follow similar patterns. If you find yourself using multiple of these in your app in your authentication process, it may be worth it looking into [FirebaseUI ](https://firebase.google.com/docs/auth/android/firebaseui)for seamless integration.
{% endhint %}

## Password Authentication

.1. Enable Email/Password sign-in:\
&#x20;    1.1. In the [Firebase console](https://console.firebase.google.com/), open the Auth section.\
&#x20;    1.2. On the **Sign in method** tab, enable the **Email/password** sign-in method and click **Save**.

![Authentication section in the console](https://195521982-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LvOeSx5ZqjJA8sxykMu%2F-MYcOknt8AWV_Djv6f1c%2F-MYcQ_rfyL_NIiop0qM0%2Fimage.png?alt=media\&token=619b4620-bb60-4368-a08e-4b6a5414d78f)

2\. When a new user signs up using your app's sign-up form, complete any new account validation steps that your app requires, such as verifying that the new account's password was correctly typed and meets your complexity requirements.

{% hint style="info" %}
EditTexts are very useful for fields for input for emails/passwords!

If you are looking for a good password strength detector, take a look at [zxcvbn](https://github.com/nulab/zxcvbn4j), which is used by Dropbox.
{% endhint %}

3\.  Create a new account by passing the new user's email address and password to `createUserWithEmailAndPassword`:

```
auth.createUserWithEmailAndPassword(email, password)
        .addOnCompleteListener(this) { task ->
            if (task.isSuccessful) {
                /**
                 * A signed in user is detected, carry out some respective actions if needed!
                 */
                val user = auth.currentUser
            } else {
                /**
                 * No user is currently signed in, can update UI elements to account for this
                 * and display some feedback to the user.
                 */
            }
        }
```

{% hint style="info" %}
Creating an account automatically signs a user in!
{% endhint %}

If given some email and password, you can sign in a user with  `signInWithEmailAndPassword:`

```
auth.signInWithEmailAndPassword(email, password)
        .addOnCompleteListener(this) { task ->
            if (task.isSuccessful) {
                /**
                 * A signed in user is detected, carry out some respective actions if needed!
                 */
                val user = auth.currentUser
                ...
             } else {
                /**
                 * No user is currently signed in, can update UI elements to account for this
                 * and display some feedback to the user.
                 */
            }
        }
```

{% hint style="info" %}
Firebase Auth offers many more ways to authenticate users, I implore you to explore the [API ](https://firebase.google.com/docs/auth)and see what could possibly fit your needs!
{% endhint %}


# 10.3 Analytics

Google Analytics is a free app measurement solution that provides insight on app usage and user engagement. Analytics reports help you understand clearly how your users behave, which enables you to make informed decisions regarding app marketing and performance optimizations. \
\
Maybe you want to know how often users are clicking a certain button on your app or how much they are using a new feature you just released. Analytics can help with that and it's super easy to set up! Think of it as having a low skill floor but a moderate skill ceiling.

<https://firebase.google.com/docs/analytics>

## Setting up Analytics

Make sure your Firebase project is set up, the steps of which are covered in the first section 11.1!

&#x20;Declare the dependency for the Analytics Android library in your **module (app-level) Gradle file:**

```
implementation 'com.google.firebase:firebase-analytics-ktx'
```

When accessing most Analytics services, you need the appropriate FirebaseAnalytics object.

```
private lateinit var firebaseAnalytics: FirebaseAnalytics
```

You can then initialize it in the onCreate method of your activity:

```
firebaseAnalytics = Firebase.analytics
```

## Using Analytics

The core of analytics is the [logEvent()](https://firebase.google.com/docs/reference/android/com/google/firebase/analytics/FirebaseAnalytics#logEvent\(java.lang.String,%20android.os.Bundle\)) function, which allows you specify the event and any additional parameters you may want to store with that event (i.e. maybe the specific article someone clicks on if your app is a news aggregator for a college campus such as Cornell[).](https://play.google.com/store/apps/details?id=com.cornellappdev.volume) Google also provides some predefined events/parameters that you can explore also (and I recommend taking a look at first)! Here's an example:&#x20;

```
firebaseAnalytics.logEvent(FirebaseAnalytics.Event.SELECT_ITEM) {
    param(FirebaseAnalytics.Param.ITEM_ID, SOME_ID)
    param(FirebaseAnalytics.Param.ITEM_NAME, SOME_NAME)
    param(FirebaseAnalytics.Param.CONTENT_TYPE, "image")
}
```

The code above logs [`SELECT_CONTENT`](https://firebase.google.com/docs/reference/android/com/google/firebase/analytics/FirebaseAnalytics.Event#SELECT_CONTENT) event when a user clicks on a specific element in your app.

Google has a lot of baseline events that can be tailored according to your needs. They cover vertical markets and business types like Retail/E-commerce, Games, etc. Check some out [here](https://support.google.com/firebase/answer/9267735?hl=en\&ref_topic=6317484)!

## Seeing data

![Example Google Analytics dashboard](https://195521982-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LvOeSx5ZqjJA8sxykMu%2F-MZ9aLZt-XbnQ7J2XNaM%2F-MZ9gwyFC8JruBJzoX0i%2Fimage.png?alt=media\&token=415aeb64-d562-4c10-ae94-ba6096259a50)

{% embed url="<https://www.techotopia.com/index.php/A_Guided_Tour_of_the_Firebase_Analytics_Dashboard>" %}

![Firebase Analytics dashboard](https://195521982-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LvOeSx5ZqjJA8sxykMu%2F-MZ9aLZt-XbnQ7J2XNaM%2F-MZ9k1P5M6mWULjTJneu%2Fimage.png?alt=media\&token=7ba78120-9329-4078-a91f-1f9738020a21)

Right off the bat, the dashboard tells you a lot of things:

1. You can see what the demographic information of your users are,
2. How regularly they use your app,
3. How much time they spend there, and
4. How much they have spent on your app.

The data captured on your app can be viewed from the dashboard in the Firebase console from here on. You’ll see predefined events that have automatically been captured by the SDK, such as active users, demographics and app engagement.

If you have both an iOS and Android version you will see the data for both of them on the dashboard. You can look at the data for just one version by clicking on ‘filters’. Filters also allow you to analyze the data for different audiences you have set up.

By default, the dashboard will show you the last 30 days of data, but you can change this by using the date picker control.

There’s a lot of different data and information on the dashboard to help you understand your app. It starts with active users i.e. how many users are in your app on a daily, weekly and monthly basis. As well as how many users are active in the last 30 minutes.

It shows user engagement, such as how much time people spend within your app. It also shows conversion metrics. If you have in-app purchasing you see this data along with information on Admob if you’re generating revenue.

There’s a section on the stability of your app. It shows the percentage of crash-free users.

Another card on the dashboard shows the latest release of your app along with the status of that release. The status allows you to click into it to investigate any issues.

## Audiences

Once the data within your app is captured via these events you've logged, you’re able to create audiences, and group users based on certain attributes like ‘purchasing users’.&#x20;

Audiences are particularly useful when used in conjunction with other Firebase features. For example, you can send custom notifications using Firebase Notifications. Notifications can even be used to target users with Google’s ad platform. Maybe you want to target users prone to cart abandonment and send them a notification to egg them on to complete a purchase.

The events data captures the overall performance of your app, but ‘user properties’ and ‘audiences’ drills down further into specific behavior. By using user properties and audiences you can segment the data captured for better insights into how your app is being used.

When you click the ‘user properties’ table you can register a property, such as the profession of your users. You then use the API to call that specific information.

You can create audiences of users who meet certain criteria and user property criteria. Once you set up the audience it will start to accumulate users who meet these criteria. You can then target these audiences with particular campaigns.

## Conclusion

And this is just the tip of the iceberg of what Google Analytics for Firebase can do for you and a good baseline as to where you can go next. I truly implore you to look into the powerhouse that is Google Analytics, as it could take your app to the next level in terms of audience and reach!

### Things to explore next

* Google Ads (using Firebase data in combination to help with targeting ads) & [**Audiences** ](https://firebase.googleblog.com/2019/01/a-crash-course-in-using-new-audiences.html)
* BigQuery
* Firebase Remote Config


# 10.4 Messaging

Firebase Cloud Messaging (FCM) is a cross-platform messaging solution that lets you reliably send messages at no cost.

<https://firebase.google.com/docs/cloud-messaging>

## In-app Messaging

{% embed url="<https://firebase.google.com/docs/in-app-messaging>" %}

![An example In-app message](https://195521982-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LvOeSx5ZqjJA8sxykMu%2F-MZcpJ3z2QWU0Iw8M48U%2F-MZiYC8z8ofq8JZ8TglL%2Fimage.png?alt=media\&token=a574af65-bf90-4124-8559-b112229e1c3f)

Firebase In-App Messaging helps you engage your app's active users by sending them targeted, contextual messages that encourage them to use key app features. For example, you could send an in-app message to get users to subscribe, watch a video, complete a level, or buy an item. You can customize messages as cards, banners, modals, or images, and set up triggers so that they appear exactly when they'd benefit your users most.

To use In-App Messaging, you must first add it's SDK to your Android app (make sure your Firebase project is set up, the steps of which are covered in the first section 11.1 and that your analytics are set up, covered in 11.3!)

```
dependencies {
    // Import the BoM for the Firebase platform
    implementation platform('com.google.firebase:firebase-bom:27.1.0')

    // Declare the dependencies for the In-App Messaging and Analytics libraries
    // When using the BoM, you don't specify versions in Firebase library dependencies
    implementation 'com.google.firebase:firebase-inappmessaging-display-ktx'
    implementation 'com.google.firebase:firebase-analytics-ktx'
}
```

To create an In-app message campaign, visit <https://console.firebase.google.com/project/_/inappmessaging/> where you can specify how your notification should look like, and who to target based on any created users from Audiences or specific event triggers from setting up your Analytics!

![Testing notifications](https://195521982-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LvOeSx5ZqjJA8sxykMu%2F-MZcpJ3z2QWU0Iw8M48U%2F-MZiZuWQP8Vc0XmA3VbG%2Fimage.png?alt=media\&token=626dc4c2-53a1-4d29-ac0f-6788eba2f43e)

When creating a notification, you can test it by specifying something called the **installation ID:**

### Get your app's installation ID

To conserve power, Firebase In-App Messaging only retrieves messages from the server once per day. That can make testing difficult, so the Firebase console allows you to specify a test device that displays messages on demand.

That testing device is determined by a [`FirebaseInstallations`](https://firebase.google.com/docs/reference/android/com/google/firebase/installations/FirebaseInstallations) ID, or FID. Find your testing app's FID by checking the Logcat in Android Studio for the following \`Info\` level log:

```
I/FIAM.Headless: Starting InAppMessaging runtime with Installation ID YOUR_INSTALLATION_ID
```


# 10.5 Firestore

No backend? No problem.


# Exporting to APK

## Standard Debug Build

Building a standard debug APK is fairly trivial, since your system is essentially doing that anyway every time you hit the **Run** button.

Through the menu, go to **Build > Build Bundle(s) / APK(s) > Build APK(s)** to create a simple debug apk file. A small prompt will pop up with the link to locate this file, however it's typically created at **\[Project Root]/app/build/outputs/apk/debug**.&#x20;

![](https://195521982-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LvOeSx5ZqjJA8sxykMu%2F-M7-FtS4gRqxTlUOhHTZ%2F-M7-ON9SQ_mGf0s28_bH%2Fimage.png?alt=media\&token=3a70bde5-1039-4f64-a1fb-30f86777ed74)

This can then be installed on any Android device manually, given several restrictions. You'll find that Android will warn you half a dozen times about "Installing from unknown sources", an option that you may need to enable in developer settings. Then you'll have to tell Google Play Protect to not quarantine the app immediately for being an unknown variable. It's only after fighting the immune system of your phone for a minute or two that it will finally let you install unverified code.

## Signed Build for Google Play Distribution

An APK for Google to distribute requires a signature, a digital marker that identifies it through the private secure key that created it. This process is a bit more involved, and creates some very important files you dare not lose.

Through the menu once more, go to **Build > Generate Signed Bundle / APK** to enter a setup assistant for this process. It will lead you through creating the keys, assigning passwords, and the exact type of file you'd like to create. For more information about this process, visit Google's documentation on [publishing your app](https://developer.android.com/studio/publish).


# Archived Native Android Textbook Pages

This section includes topics focused on Native Android development that was used in the course is previous semesters, that are now outdated with the use of Jetpack Compose.


# 1. Layouts and More Views

> "**Design is not just what it looks and feels like. Design is how it works”** - Steve Jobs.

Yes we are quoting the founder of Apple -- the man preaches the truth. 👏 At this point in our Android education, all we can do is some Kotlin manipulation and outputting of Strings to Logcat, which probably won’t be enough to get us millions of users.&#x20;

We have to make things actually work and give our users something to see, which is where the UI comes into play!

## Topics

* File Structure
* Resource Files
* Button and Input Controls
* ViewGroups


# 1.1 File Structure and File Types

In general, each Activity (think of it as a unique screen displayed) in an Android app has two files associated with it: a Kotlin / Java file that manages the logic behind the screen, and an XML file which makes the visual display that is shown to the user.&#x20;

## XML Files

The XML files associated with this project are located under `app/res/layout`. At first the code might seem intimidating, but they’re not difficult to write once you understand how they are formatted. XML files contain a `ViewGroup` object that contains `View` objects inside. As you will learn in the next section, view groups set the layout or structure for how views will be placed on the screen.&#x20;

Below, is an example of what a simple XML file might look like. The majority of the code that is displayed below is auto-generated by Android Studio when you create a new XML file. What is important to note is that this file contains a layout that contains one component with a text element inside.&#x20;

{% code title="activity\_main.xml" %}

```markup
<FrameLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
    <TextView
        android:id="@+id/textView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Hello World"/>
</FrameLayout>
```

{% endcode %}

Inside this file, we can see that there is a hierarchical 'tree’ of elements. Each element has an opening tag  (`<FrameLayout ...>`) and a closing tag (`</FrameLayout>`), and the code in between these two tags are all the ‘children’ of that element. The lines of code within the opening tag represent a set of **attributes** associated with each element. Each attribute has a name (i.e. `android:text`) that is set equal to a value (i.e. “Hello World”). Attributes can help you customize the placement and styling of an element.&#x20;

![](https://195521982-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LvOeSx5ZqjJA8sxykMu%2F-LvZH3TGSzyS8bKiA1lT%2F-LvZIk-voMv230F9P6AV%2FScreen%20Shot%202019-12-08%20at%201.58.50%20AM.png?alt=media\&token=b0e7ca3a-1f38-4f49-bfee-871999092da5)

Taking a look at the example above, the first two attributes of the `TextView` element set the **width** to be as large as the parent (`FrameLayout`) and the **height** to be as large as the content inside of it (the height and the width must be defined for every XML element). The last attribute sets the text of the `TextView` to be “Hello World!”.  Other attributes for this `TextView` include `android:textColor`, and `android:layout_marginLeft`  which changes how much space is to the left of the element.&#x20;

The `tools:context=".MainActivity"` attribute of the `FrameLayout` defines what class this XML file is connected to. Note the **id** attribute that we added as well—it is a vital attribute that allows us to identify and manipulate the component from the specific class defined for this XML file.&#x20;

## Class File

The complement to `activity_main.xml` is a file called `MainActivity.kt`, and this class will control all the logic associated with the screen generated by `activity_main.xml`. The code below is auto-generated when you create a new "Empty Activity" project.

{% code title="MainActivity.kt" %}

```kotlin
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }
}
```

{% endcode %}

The line `setContentView()` inside the `onCreate()` method allows for the XML layout we defined above to be shown when the activity is launched in the application. Method `findViewById(R.id.textView)` is a built-in method that allows users to access and add functionality to a component described in the associated XML file with a given `id`. Then, after storing the element in a local variable, we dynamically update the text to display "Hello Friend".

Having a good understanding of Kotlin files, XML files, and their interactions is a core skill necessary for Android development. In this section, we looked at an overview of XML and Kotlin files, and how to manipulate a `TextView` element through XML and through Kotlin code.&#x20;

For the rest of the chapter, we’ll be focusing on more complex elements (buttons, text inputs, etc.) that you have at your disposal.


# 1.2 Resource Files

Before ending the chapter, we ought to understand some of the other files laying around in the res folder. These are the resource files of our project, and Android resource files are split into these sub-categories:<br>

1. **Drawable** contains images and icons for app in the phone’s home screen.
2. **Font** contains custom font files
3. **Layout** contains XML files for all the layouts in the project
4. **Mipmap** contains icon images
5. **Raw** contains audio and video media
6. **Values** contains reusable values for colors, dimens, strings, and styles

\
For the most part, these resource files will remain empty until you start populating them with information, and it is generally good practice to move resources into the resource file if that resource going to be used multiple times. Along the same line, it’s good practice to move commonly used values into the resource files so that if the design of the app changes, you won’t have to go through every XML and class file to change the values. &#x20;

For example, in order to avoid having to remember specific color codes, you can specify them in the `colors.xml` file. Now the color can be referenced by its string name.&#x20;

{% code title="colors.xml" %}

```markup
<resources>
  <color name="colorPrimary">#008577</color>
  <color name="colorPrimaryDark">#00574B</color>
  <color name="colorAccent">#D81B60</color>
</resources>
```

{% endcode %}

You would refer to the colors in the XML file like so:

```markup
android:background="@color/colorPrimary"
```

This way, if the designer decides to change up the color scheme of the app, you would only have to change three values.&#x20;


# 1.3 Button and Input Control

## Input controls

There are views that exist specifically to accept data input. Here are a few of the more commonly used input controls available to you:

![User input controls ](https://google-developer-training.github.io/android-developer-fundamentals-course-concepts-v2/images/4-2-c-input-controls/user_input_controls_composite.png)

1. `EditView`: used to input and edit text
2. `SeekBar`: used to represent a range of inputs
3. `CheckBox`: used for selecting multiple options
4. `RadioGroup` and `RadioButton`: used for selecting one option
5. `Switch`: used to enable and disable functions
6. `Spinner`: used to select an item from a list

Great that all these elements exist! Now how do we actually implement them?

## Implementing a Button

Buttons are one of the most fundamental components in Android allowing the user to interact with the screen. The input controls above are because a button must take in user feedback and change the view accordingly, implementing a button requires us to add code to both the XML file and the class file.

\
Here’s what a button may look like when it is added to the XML file. You'll notice that we set the three important attributes: height, width, and id.

{% code title="activity\_main.xml" %}

```markup
<Button
     android:layout_width="wrap_content"
     android:layout_height="wrap_content" 
     android:id="@+id/first_button"
     <!-- more attributes ... --> 
 />
```

{% endcode %}

In our class, we just need to get a reference to the `Button` object and set an on click listener so that the button can function if the user clicks on it. Kotlin has auto-generated a variable for it with the same name as the id, so remember to use unique and understandable names for those.

{% code title="MainActivity.kt" %}

```kotlin
first_button.setOnClickListener(){
    // Do something in response to button click
}
```

{% endcode %}

It is possible to set the `onClick` attribute directly in the XML files. We advise against setting that attribute there, as it’s much cleaner to keep all logic related code out of the XML files.

## Other Listeners

Each input control has its own set of listeners and functions that are used to change its behavior. Often times, the best way to find out what listeners are available to you is to start typing `“inputView.set…”` and scroll through the auto-complete options, where `inputView` is a pointer to a View.&#x20;

The official Android documentation on what different listeners are available for each element is also quite good. Here is an example of how we would add a listener to perform actions when the text inside an `EditText` changes:&#x20;

```kotlin
editText.addTextChangedListener(object : TextWatcher {
 
    override fun afterTextChanged(s: Editable) {
       // Do Something
    }
 
    override fun beforeTextChanged(s: CharSequence, start: Int,
                                   count: Int, after: Int) {
       // Do Something
    }
 
    override fun onTextChanged(s: CharSequence, start: Int,
                               before: Int, count: Int) {
       // Do Something
    }
})
```

Hopefully you now have a larger inventory of views and input controls to use in your forays into the Android World.&#x20;


# 1.4 ViewGroups

As mentioned in section 2.1, there are two major types of components in Android XMLs: views and view groups. A `ViewGroup`essentially describes the layout and structure of a set of `View` objects. Typically, if you want to display a set of elements in a unique way in relation to one another, a parent `ViewGroup` object is created to define the relationship.

Some of the most common `ViewGroups` you’ll run into are the following:

## Linear Layout

These are the most light-weight and commonly used `ViewGroup`. The default **orientation** of these layouts is horizontal, i.e. any child elements placed inside them will be displayed in a horizontal line.&#x20;

\
Changing the attribute `android:orientation` of the `LinearLayout` to "vertical" will force the `LinearLayout` to display the child views vertically rather than horizontally. <br>

![Linear Layout Horizontal vs Vertical Orientations](https://paper-attachments.dropbox.com/s_271C12D1E64C9170FA4FD7A4C58872DFE15AABB42A29FFB4F198A2919A9B444F_1574310267306_liner.jpg)

Because this layout is fairly inexpensive for the compiler to build, you’ll see this layout used everywhere, especially for layouts with list-like properties.

## Relative Layout&#x20;

In contrast to `LinearLayout`, which can only place views in a line, this `ViewGroup` gives the developer more control over the precise positioning of child views. Positioning attributes are added to the child views of `RelativeLayout`, like so:

```markup
android:layout_toRightOf="@id/elementA" 
```

This attribute will position the child component immediately to the right of the element with id “elementA” (elementA should be a *direct* child of the same `RelativeLayout`). We can do the same thing for positioning elements to the left, above, and below.

\
We can also center the child elements to the middle of the parent view (vertically), with the attribute `android:layout_centerVertical="true"`**.** Remember, these constraints can only be added to child components who are encompassed inside a `RelativeLayout` in the hierarchical tree of XML.

\
For the `RelativeLayout` itself, adding the attribute `android:gravity` moves all child elements into one pattern or location within the `RelativeLayout`.

&#x20;\
While `RelativeLayout` is convenient to use, it is important to note that it is also the most expensive layout to build (meaning that the compiler has to make extra computations to create the layout). This is why we try to avoid using `RelativeLayout` if possible.&#x20;

## **Constraint Layout**&#x20;

`ConstraintLayout` is similar to `RelativeLayout`, but has better performance and (generally) more flexibility (not to mention that it is the layout most recommended by Google). Each direct child view in a `ConstraintLayout` needs both a horizontal and a vertical constraint. Let’s take a look at the following pair of attributes:

```markup
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@id/bigButton"
```

The first constraint will constrain the child to be vertically attached to the bottom of the `ConstraintLayout`, and the second constraint will position the right side of the child to be in line with the right side of element *bigButton*. Note here that the terms **Start** and **End** refer to **Left** and **Right** constraints, respectively.

If we didn’t want the constraints to be directly in line with these elements, we can add the following attributes to the same child component above:<br>

```
android:layout_marginEnd="50dp"
android:layout_marginBottom="70dp"
```

These four properties in combination will now position the right side of our element 50 density-independent pixels (dp) to the left of the right side of *bigButton*, and will position our element 70dp above the bottom of the screen. Margins are a very useful attribute that are used all over the place, not just in `ConstraintLayout`.

## **Frame Layout**&#x20;

`FrameLayout` is a flexible layout that allows child views to be stacked on top of each other based on the order of when the views are added, similar to how someone may layer photographs inside of a picture frame.

![Example of how FrameLayout stacks views](https://195521982-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LvOeSx5ZqjJA8sxykMu%2F-LvZH3TGSzyS8bKiA1lT%2F-LvZkl5xSYt6FOUA0K-v%2FScreen%20Shot%202019-12-08%20at%204.06.31%20AM.png?alt=media\&token=502234a2-2c96-4cc6-8d49-1465f2ce6280)

Because `FrameLayout` is very similar to a `ViewGroup` and doesn’t have any custom attributes like `LinearLayout` or `RelativeLayout`, it is used often as a fragment container (will be explained in chapter 4) or as a container to hold multiple views that may be swapped out.

## **ScrollView / RecyclerView / ListView - A Soft Intro**

These layouts all display some sort of scrollable content to users. A `ScrollView` is similar to a vertical `LinearLayout` in their organization of displaying child elements, but simply makes the child content scrollable if the total height of the child elements extends beyond the height of the `ScrollView`.

List views and recycler views are used to display a scrollable list of elements that are set dynamically. They are meant to create a list of varying lengths based on an array of information, whereas scroll views children are normally set in the `layout.xml` files. These `ViewGroup` objects also are more efficient in that they only render as much child content as needed to display to the user, and only when the user scrolls to new areas are those child elements rendered.

As for comparing these two, recycler views are generally thought of as a better version (simpler, more efficient, easier to use) of list views. These views become very important to development, and will be elaborated upon in Chapter 4.


# 1.5 Summary + A Note On Chapter 2 Topics

Having a good understanding of class files, XML files, and their interactions is a core skill for Android development. In this chapter, we discussed several of the most common views, and typical ways of adding functionality in class files and organizing them using `ViewGroup` objects. Then, we looked at some of the other resource file types that Android developers typically include in their applications. We will keep seeing ViewGroups again and again so if it seems a little confusing now, they'll become more and familiar soon, just try to master the one you use this week by exploring! On the note of exploration...&#x20;

There are also a bajillion more views than the ones discussed in this chapter, which I’m sure you’ll come across in your quest for Android knowledge when exploring. Experience, googling, and trial-and-error will be your best friends in being able to develop some 🔥 applications.


# 2. RecyclerViews

Try to think of an app you use daily that doesn’t have a list you can scroll through - I’ll give you a minute. Couldn't think of an app? Don't worry, me too.&#x20;

From maps to messaging apps, almost every mobile app has a scrollable list as the main layout. Lists are by far the most natural way to interact with a set of data that cannot fit within a single screen, making lists one of the most fundamental components in Android.&#x20;

In this chapter, we'll be covering `RecyclerView`

## Topics

* RecyclerView
* Performance
* Implementation of a RecyclerView
* Implementation with Input Controls
* Filtering


# 2.1 RecyclerViews

Lists are one of the most ubiquitous layouts that can be found in any mobile app. All of the screenshots below show different usages of a list-like layout. In Facebook, lists are used to display each post in the news feed and allow a user to engage with the post through likes and comments. In Podbean, each cell in the list is clickable and leads the user to the specific podcast's page. Finally, in Venmo, the list just displays information on past transactions with some user interactions supported.

![](https://195521982-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LvOeSx5ZqjJA8sxykMu%2F-LvTjKITgwBiIJ7g18un%2F-LvTml59yJE5FYdTNncZ%2Fimage.png?alt=media\&token=815e7cf5-ebe7-4d9e-a8ef-c1bbaa4e9f7e)

RecyclerView makes it easy to efficiently display large sets of data. You supply the data and define how each item looks, and the RecyclerView library dynamically creates the elements when they're needed.

As the name implies, RecyclerView *recycles* those individual elements. When an item scrolls off the screen, RecyclerView doesn't destroy its view. Instead, RecyclerView reuses the view for new items that have scrolled onscreen. This reuse vastly improves performance, improving your app's responsiveness and reducing power consumption (more on this in the next section, [2.2 RecyclerView Performance](/archive/archived-native-android-textbook-pages/4.-listview-and-recyclerview/4.3-performance)).


# 2.2 RecyclerView Performance

For a RecyclerView, each item in the dataset is represented by a cell in the list. Only the cells that are immediately visible to the user are created at runtime; the hidden cells will be rendered as you scroll. This is how the compiler handles the rendering of the list.&#x20;

### Performance with RecyclerView

The recycler view only creates the cells that are immediately visible to the user, but the recycler view utilizes the `ViewHolder` pattern, a holder for cell metadata that maximizes performance by reducing the amount of times that `view.findViewById()` is called by the system. As the user scrolls, the recycler view will either create new view holders as necessary or reuse the view holders that have been offscreen the longest. By reusing view holders, we can reduce the number of calls to `view.findViewById()`.

### Why use RecyclerView over Alternatives?

`RecyclerView` has many in-app memory optimization that is already completed for developers. For example, `RecyclerView` forces developers to use the `ViewHolder` pattern.&#x20;

`RecyclerView` was created as a direct enhancement to `ListView`  which we will learn about in lecture 5 and thus, `RecyclerView` is easier to use and manipulate for complex lists. However, as you will see in Lecture 5, there will be instances where it makes more sense to use a `ListView` to save development time, like with the menu in Eatery. We'll go into more detail about this in the next section.&#x20;


# 2.3 Implementation of a Recycler View

### Key classes <a href="#key-classes" id="key-classes"></a>

Several different classes work together to build your dynamic list.

* **`RecyclerView`** is the `ViewGroup` that contains the views corresponding to your data. It's a view itself, so you add `RecyclerView` into your layout the way you would add any other UI element.
* Each individual element in the list is defined by a *view holder* object. When the view holder is created, it doesn't have any data associated with it. After the view holder is created, the `RecyclerView` *binds* it to its data. You define the view holder by extending `RecyclerView.ViewHolder`.
* The `RecyclerView` requests those views, and binds the views to their data, by calling methods in the *adapter.* You define the adapter by extending `RecyclerView.Adapter`.
* The *layout manager* arranges the individual elements in your list. You can use one of the layout managers provided by the RecyclerView library, or you can define your own. Layout managers are all based on the library's `LayoutManager` abstract class.

### Steps for implementing your RecyclerView <a href="#steps-for-implementing" id="steps-for-implementing"></a>

* Design how each element in the list is going to look and behave in some layout file. Based on this design, extend the `ViewHolder` class. Your version of `ViewHolder` provides all the functionality for your list items. Your view holder is a wrapper around a `View`, and that view is managed by `RecyclerView`.
* Define the `Adapter` that associates your data with the `ViewHolder` views.

### Implementing your adapter and view holder

When you define your adapter, you need to override three key methods:

* [`onCreateViewHolder()`](https://developer.android.com/reference/androidx/recyclerview/widget/RecyclerView.Adapter#onCreateViewHolder\(android.view.ViewGroup,%20int\)): `RecyclerView` calls this method whenever it needs to create a new `ViewHolder`. The method creates and initializes the `ViewHolder` and its associated `View`, but does *not* fill in the view's contents—the `ViewHolder` has not yet been bound to specific data.
* [`onBindViewHolder()`](https://developer.android.com/reference/androidx/recyclerview/widget/RecyclerView.Adapter#onBindViewHolder\(VH,%20int\)): `RecyclerView` calls this method to associate a `ViewHolder` with data. The method fetches the appropriate data and uses the data to fill in the view holder's layout. For example, if the `RecyclerView` displays a list of names, the method might find the appropriate name in the list and fill in the view holder's [`TextView`](https://developer.android.com/reference/android/widget/TextView) widget.
* [`getItemCount()`](https://developer.android.com/reference/androidx/recyclerview/widget/RecyclerView.Adapter#getItemCount\(\)): RecyclerView calls this method to get the size of the data set. For example, in an address book app, this might be the total number of addresses. RecyclerView uses this to determine when there are no more items that can be displayed.

First we introduce the data model that will be used for the rest of this lesson:

```kotlin
data class Book(var bookName: String, var author: String, val publisher: String)
```

Here's a typical example of a simple adapter with a nested `ViewHolder` that displays a list of data. In this case, the RecyclerView displays a simple list of Book elements. The adapter is passed an array of books, containing the data needed for the `ViewHolder` elements:

```kotlin
class CustomAdapter(private val dataSet: List<Book>) :
        RecyclerView.Adapter<CustomAdapter.ViewHolder>() {

    /**
     * Provide a reference to the type of views that you are using
     * (custom ViewHolder).
     */
    class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
        val bookName: TextView = view.findViewById(R.id.book_name)
        val author: TextView = view.findViewById(R.id.author)
        val publisher: TextView = view.findViewById(R.id.publisher)
    }

    // Create new views (invoked by the layout manager)
    override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): ViewHolder {
        // Create a new view, which defines the UI of the list item
        val view = LayoutInflater.from(viewGroup.context)
                .inflate(R.layout.book_row_item, viewGroup, false)
        return ViewHolder(view)
    }

    // Replace the contents of a view (invoked by the layout manager)
    override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
        // Get element from your dataset at this position and replace the
        // contents of the view with that element
        viewHolder.bookName.text = dataSet[position].bookName
        viewHolder.author.text = dataSet[position].author
        viewHolder.publisher.text = dataSet[position].publisher
    }

    // Return the size of your dataset (invoked by the layout manager)
    override fun getItemCount() = dataSet.size
}

```

The snippet above can be tailored to your specific needs in terms of whatever data you are trying to display as a list with RecyclerView!

The layout for the each view item is defined in an XML layout file, similarly as we do for activities. In this case, the app has a `book_row_item.xml` file like this:

```xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/layout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:cardCornerRadius="12dp"
    app:cardElevation="10dp"
    app:cardUseCompatPadding="true">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="110dp"
        android:background="@color/white">

        <TextView
            android:id="@+id/book_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginVertical="10dp"
            android:layout_marginStart="10dp"
            android:textSize="20sp"
            android:textStyle="bold"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <TextView
            android:id="@+id/author"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginVertical="10dp"
            android:layout_marginStart="10dp"
            android:textSize="17sp"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@id/book_name" />

        <TextView
            android:id="@+id/publisher"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginVertical="10dp"
            android:layout_marginStart="10dp"
            android:textSize="17sp"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@id/author" />
    </androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>

```

In this case, I use a new `ViewGroup` called `CardView` to build beautiful row items for our `RecyclerView`! `CardView` is an extended version of `Framelayout` which can be used to show items inside the card format. With the help of `CardView`, we can add radius, elevation to our items of `RecyclerView`. `CardView` gives a rich look and feel to our list of data. Check [this ](https://developer.android.com/guide/topics/ui/layout/cardview)out to learn more!

![CardView row for our Book objects](https://195521982-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LvOeSx5ZqjJA8sxykMu%2Fuploads%2F2cf7eIgkyFAMdp9XhGTV%2Fimage.png?alt=media\&token=c051766c-9b52-41a3-b2e6-6d8bd145e249)

### Integrating RecyclerView with Activity

1\. We can add `RecyclerView` to our layout file as such:

{% code title="my\_activity.xml" %}

```markup
<?xml version="1.0" encoding="utf-8"?>
<!-- A RecyclerView with some commonly used attributes -->
<androidx.recyclerview.widget.RecyclerView
    android:id="@+id/my_recycler_view"
    android:scrollbars="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>
```

{% endcode %}

2\. Integrate it into activity:

{% code title="MyActivity.kt" %}

```kotlin
class MyActivity : AppCompatActivity() {
    private lateinit var recyclerView : RecyclerView

    override fun onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.my_activity)
        
        recyclerView = findViewById<RecyclerView>(R.id.my_recycler_view)

        // use this setting to improve performance if you know that changes
        // in content do not change the layout size of the RecyclerView
        recyclerView.setHasFixedSize(true)

        // use a linear layout manager
        recyclerView.layoutManager = LinearLayoutManager(this)

        // create dataset, format should match what you specified 
        // in the MyAdapter object
        var myDataset = mutableListOf<Book>()
        
        // Populate your myDataset with your data
        
        ...
        
        recyclerView.adapter = CustomAdapter(myDataset)
    }
    // ...
}
```

{% endcode %}

### Other LayoutManagers

In MyActivity.kt, I simply used a LinearLayoutManager to arrange our RecyclerView items. LinearLayoutManager arranges the items in a one-dimensional list but there are other managers available!

* [`GridLayoutManager`](https://developer.android.com/reference/androidx/recyclerview/widget/GridLayoutManager) arranges all items in a two-dimensional grid:
  * If the grid is arranged vertically, `GridLayoutManager` tries to make all the elements in each row have the same width and height, but different rows can have different heights.
  * If the grid is arranged horizontally, `GridLayoutManager` tries to make all the elements in each column have the same width and height, but different columns can have different widths.
* [`StaggeredGridLayoutManager`](https://developer.android.com/reference/androidx/recyclerview/widget/StaggeredGridLayoutManager) is similar to `GridLayoutManager`, but it does not require that items in a row have the same height (for vertical grids) or items in the same column have the same width (for horizontal grids). The result is that the items in a row or column can end up offset from each other.
* One can also create a customized Layout Manager!

![Linear Layout, Grid Layout, Staggered Grid Layout, Custom Grid Layout](https://195521982-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LvOeSx5ZqjJA8sxykMu%2Fuploads%2FuarNhj9ttlXVxg5AYH81%2Fimage.png?alt=media\&token=9187a287-b0a0-44f7-b498-4b29bb3d3c4f)

This is the bare bone code that is needed to create a recycler view list in Android. In the next section, we’ll be delving into an example of adding touch input.


# 2.4 Implementation with Input Controls

## Callback Interface

Often times, we’ll want to use the items in the `RecyclerView` as an entry point to another screen and the simplest way to do so is through an interface inside the adapter class.

{% hint style="info" %}
For this example, we'll be building on top of the code shown in section 4.3.
{% endhint %}

1\. We can define our own `onClick()` method in our interface and it can take in as many as parameters as we need.&#x20;

In this example, our click listener will just take in the position of the item on the list. Just remember that if you plan to use the `onClick()` method to start a new fragment or activity that you may want to pass in enough information, like a list or a key, to be able to make the correct computation.

{% code title="CustomAdapter.kt" %}

```kotlin
class CustomAdapter(
    private val dataSet: Array<Book>,
    private val mAdapterOnClickHandler: AdapterOnClickHandler
  )
    : RecyclerView.Adapter<CustomAdapter.ViewHolder>() {

  interface AdapterOnClickHandler {
    // you can define the parameters to be what you need
    fun onClick(position: Int)
  }
}
```

{% endcode %}

2\. Inside onBindViewHolder we can customize the onClickListeners of our various views to refer to the callback functions we just created! Below, we override the onClickListener of a button defined in our cell! We pass whatever is specified by the onClick method from our button.

{% code title="CustomAdapter.kt" %}

```kotlin
override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
    viewHolder.button.setOnClickListener {
        mAdapterOnClickHandler.onClick(position)
    }
}
```

{% endcode %}

3\. In our activity or fragment, wherever the adapter is actually created, we’ll be implementing the `onClick()` method to our custom listener. In this example, the adapter will start a new activity when the item is clicked on.

Don't forget to extend the interface as we did at the header of the MainActivity! Android Studio will automatically tell you to implement the given functions once you do!

{% code title="MainActivity.kt" %}

```kotlin
class MainActivity : AppCompatActivity(),
   CustomAdapter.AdapterOnClickHandler {
   
   override fun onCreate(Bundle savedInstanceState) {
    ...
    recyclerView.adapter = CustomAdapter(myDataset, this)
    ...
  }
  
  override fun onClick(position: Int) {
    // add code for action to happen on click of the button in your RecyclerView row
    var i = Intent(this, SomeOtherActivity::class.java)
    i.putExtra("position", position)
    startActivity(i)
  }
}
```

{% endcode %}

## Higher-order functions

If your interface has a small amount of functions (i.e. one or two), the above can be simplified using higher-order functions!

Kotlin functions are [first-class](https://en.wikipedia.org/wiki/First-class_function), which means they can be stored in variables and data structures, and can be passed as arguments to and returned from other [higher-order functions](https://kotlinlang.org/docs/lambdas.html#higher-order-functions). You can perform any operations on functions that are possible for other non-function values.

Instead of defining some interface, we can pass the function in directly when creating a new adapter:

```kotlin
class CustomAdapter(
    private val dataSet: Array<Book>,
    private val onItemClick: (Int) -> Unit
  )
    : RecyclerView.Adapter<CustomAdapter.ViewHolder>() {
    
    override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
    viewHolder.button.setOnClickListener {
        onItemClick(position)
    }
}
```

When initializing our adapter in our activity, we can pass in a function with the corresponding type or define it directly:

```kotlin
// An example that defines the function directly
val adapter = CustomAdapter(myDataset) { position -> 
   // The click action you want to perform.
}
```


# 2.5 Filtering RecyclerViews

## Creating a Filter

In order to support filtering items in our RecyclerView, we must change a few things. Take our CustomAdapter from [2.3 Implementation of a Recycler View](/archive/archived-native-android-textbook-pages/4.-listview-and-recyclerview/4.4-implementation-of-recyclerview):

```kotlin
class CustomAdapter(private val dataSet: List<Book>) :
        RecyclerView.Adapter<CustomAdapter.ViewHolder>() {

    /**
     * Provide a reference to the type of views that you are using
     * (custom ViewHolder).
     */
    class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
        val bookName: TextView = view.findViewById(R.id.book_name)
        val author: TextView = view.findViewById(R.id.author)
        val publisher: TextView = view.findViewById(R.id.publisher)
    }

    // Create new views (invoked by the layout manager)
    override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): ViewHolder {
        // Create a new view, which defines the UI of the list item
        val view = LayoutInflater.from(viewGroup.context)
                .inflate(R.layout.book_row_item, viewGroup, false)
        return ViewHolder(view)
    }

    // Replace the contents of a view (invoked by the layout manager)
    override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
        // Get element from your dataset at this position and replace the
        // contents of the view with that element
        viewHolder.bookName.text = dataSet[position].bookName
        viewHolder.author.text = dataSet[position].author
        viewHolder.publisher.text = dataSet[position].publisher
    }

    // Return the size of your dataset (invoked by the layout manager)
    override fun getItemCount() = dataSet.size
}

```

In the **RecyclerView adapter**, create an **ArrayList** with the name **dataSetFiltered**, and pass all the items from our original list. The rest of the adapter is now based off of this filtered list as opposed to the original data set (which should remain unchanged, acting as a source of truth for what an unfiltered list looks like):

```kotlin
class CustomAdapter(private val dataSet: List<Book>) :
        RecyclerView.Adapter<CustomAdapter.ViewHolder>() {
        
     private var dataSetFiltered: List<Book> = dataSet
     
     ...
     
 }  
```

Return the size of the dataSetFiltered as opposed to the dataSet so the right amount of items to display matches what's filtered:

```kotlin
class CustomAdapter(private val dataSet: List<Book>) :
        RecyclerView.Adapter<CustomAdapter.ViewHolder>() {
        
     private var dataSetFiltered: List<Book> = dataSet
     
     ...
     
     override fun getItemCount(): Int = dataSetFiltered.size
 }  
```

Now, in the **onBindViewHolder** get the item for each row from the **dataSetFiltered** list:

```kotlin
class CustomAdapter(private val dataSet: List<Book>) :
        RecyclerView.Adapter<CustomAdapter.ViewHolder>() {

     private var dataSetFiltered: List<Book> = dataSet
     
     ...
     
     // Replace the contents of a view (invoked by the layout manager)
    override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
        // Get element from your dataset at this position and replace the
        // contents of the view with that element
        viewHolder.bookName.text = dataSetFiltered[position].bookName
        viewHolder.author.text = dataSetFiltered[position].author
        viewHolder.publisher.text = dataSetFiltered[position].publisher
    }
     
     override fun getItemCount(): Int = dataSetFiltered.size
}

```

Lastly, add the capabilities to filter based on some query!

Add the filter variable to your adapter:

```kotlin
class CustomAdapter(private val dataSet: List<Book>) :
        RecyclerView.Adapter<CustomAdapter.ViewHolder>() {

     private var dataSetFiltered: List<Book> = dataSet
     
     var filterTest: CharSequence = ""
        set(value) {
            field = value
            onFilterChange()
        }
     
     ...
     
     // Replace the contents of a view (invoked by the layout manager)
    override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
        // Get element from your dataset at this position and replace the
        // contents of the view with that element
        viewHolder.bookName.text = dataSetFiltered[position].bookName
        viewHolder.author.text = dataSetFiltered[position].author
        viewHolder.publisher.text = dataSetFiltered[position].publisher
    }
     
     override fun getItemCount(): Int = dataSetFiltered.size
}
```

Next, we have to implement onFilterChange:

```kotlin
class CustomAdapter(private val dataSet: List<Book>) :
        RecyclerView.Adapter<CustomAdapter.ViewHolder>() {

     private var dataSetFiltered: List<Book> = dataSet
     
     var filterTest: CharSequence = ""
        set(value) {
            field = value
            onFilterChange()
        }
     
     private fun onFilterChange() {
        dataSetFiltered = if (charString.isEmpty()) {
            // There's no query, should return back the unfiltered list. 
            dataSet 
        } else {
            // The filter function returns a list containing only elements 
            // matching the given predicate. In this case, we can choose 
            // our predicate to be what we want to filter by. Since we are working
            // with books, let's allow filtering by author and title!
            dataSet.filter { book ->
                 book.bookName.contains(charString) || book.author.contains(charString)
            }
        }
        
        notifyDataSetChanged()
     }
     
     // Replace the contents of a view (invoked by the layout manager)
    override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
        // Get element from your dataset at this position and replace the
        // contents of the view with that element
        viewHolder.bookName.text = dataSetFiltered[position].bookName
        viewHolder.author.text = dataSetFiltered[position].author
        viewHolder.publisher.text = dataSetFiltered[position].publisher
    }
     
     override fun getItemCount(): Int = dataSetFiltered.size
}
```

{% hint style="info" %}
Using notifyDataSetChanged() is costly and often inefficient. We can take advantage of the [DiffUtil](https://developer.android.com/reference/androidx/recyclerview/widget/DiffUtil) Android utility class to make updating our RecyclerView more efficient.&#x20;
{% endhint %}

## Using the Filter

To use filter, we can simply set the value of the filter in our adapter:

```kotlin
adapter.filter = someText
```

We can also leverage [SearchView](https://developer.android.com/reference/android/widget/SearchView) to filter our RecyclerView by query from the user.


# 2.6 Recyclerview Demo

Code that contains a RecylerView with customized adapter and two types of OnClickListeners.

{% file src="/files/w6n6o2spaGeNksDd5dNc" %}


# 3. ListViews and Searching

Like Recycler Views, but easier to code lol


# 3.1 ListView vs. RecyclerView

`ListView` and `RecyclerView` are two different layouts that can be used to display a list of objects, but it is often difficult to tell which one is being used in a mobile app.&#x20;

Typically the implementation of a `ListView` is simpler than a `RecyclerView` but are less performant. For apps like those you built in HW4 and will build in HW5 where there are minimal calculations, a `ListView` is perfectly serviceable.&#x20;

We used both types of layouts in Eatery:&#x20;

![](https://195521982-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LvOeSx5ZqjJA8sxykMu%2F-LvTjKITgwBiIJ7g18un%2F-LvTosamzTLm-NKFGFsL%2Fimage.png?alt=media\&token=0100d70b-71e2-48ef-883c-e12e8cad7862)

Looking at a list in an app for the first time, it’s difficult to tell whether the screen is made with a list view or a recycler view. In fact, to the user, there is **almost no visual difference** between a list made with either layouts.&#x20;

The main difference between the two layouts stems from how they handle performance. In the industry, it is highly recommended to use a recycler view unless you are building a very simple list that does not require heavy computations, as previously mentioned.




---

[Next Page](/llms-full.txt/1)

