|
| 1 | +--- |
| 2 | +title: Scaffold a new Android project |
| 3 | +weight: 2 |
| 4 | + |
| 5 | +### FIXED, DO NOT MODIFY |
| 6 | +layout: learningpathall |
| 7 | +--- |
| 8 | + |
| 9 | +This learning path will teach you to architect an app following [modern Android architecture](https://developer.android.com/courses/pathways/android-architecture) design with a focus on the [UI layer](https://developer.android.com/topic/architecture/ui-layer). |
| 10 | + |
| 11 | +## Development environment setup |
| 12 | + |
| 13 | +Download and install the latest version of [Android Studio](https://developer.android.com/studio/) on your host machine. |
| 14 | + |
| 15 | +This learning path's instructions and screenshots are taken on macOS with Apple Silicon, but you may choose any of the supported hardware systems as described [here](https://developer.android.com/studio/install). |
| 16 | + |
| 17 | +Upon first installation, open Android Studio and proceed with the default or recommended settings. Accept license agreements and let Android Studio download all the required assets. |
| 18 | + |
| 19 | +Before you proceed to coding, here are some tips that might come handy: |
| 20 | + |
| 21 | +{{% notice Tip %}} |
| 22 | +1. To navigate to a file, simply double-tap `Shift` key and input the file name, then select the correct result using `Up` & `Down` arrow keys and then tap `Enter`. |
| 23 | + |
| 24 | +2. Every time after you copy-paste a code block from this learning path, make sure you **import the correct classes** and resolved the errors. Refer to [this doc](https://www.jetbrains.com/help/idea/creating-and-optimizing-imports.html) to learn more. |
| 25 | +{{% /notice %}} |
| 26 | + |
| 27 | +## Create a new Android project |
| 28 | + |
| 29 | +1. Navigate to **File > New > New Project...**. |
| 30 | + |
| 31 | +2. Select **Empty Views Activity** in **Phone and Tablet** galary as shown below, then click **Next**. |
| 32 | + |
| 33 | + |
| 34 | +3. Proceed with a cool project name and default configurations as shown below. Make sure that **Language** is set to **Kotlin**, and that **Build configuration language** is set to **Kotlin DSL**. |
| 35 | + |
| 36 | + |
| 37 | +### Introduce CameraX dependencies |
| 38 | + |
| 39 | +[CameraX](https://developer.android.com/media/camera/camerax) is a Jetpack library, built to help make camera app development easier. It provides a consistent, easy-to-use API that works across the vast majority of Android devices with a great backward-compatibility. |
| 40 | + |
| 41 | +1. Wait for Android Studio to sync project with Gradle files, this make take up to several minutes. |
| 42 | + |
| 43 | +2. Once project is synced, navigate to `libs.versions.toml` in your project's root directory as shown below. This file serves as the version catalog for all dependencies used in the project. |
| 44 | + |
| 45 | + |
| 46 | + |
| 47 | +{{% notice Info %}} |
| 48 | + |
| 49 | +For more information on version catalogs, please refer to [this doc](https://developer.android.com/build/migrate-to-catalogs). |
| 50 | + |
| 51 | +{{% /notice %}} |
| 52 | + |
| 53 | +3. Append the following line to the end of `[versions]` section. This defines the version of CameraX libraries we will be using. |
| 54 | +```toml |
| 55 | +camerax = "1.4.0" |
| 56 | +``` |
| 57 | + |
| 58 | +4. Append the following lines to the end of `[libraries]` section. This declares the group, name and version of CameraX dependencies. |
| 59 | + |
| 60 | +```toml |
| 61 | +camera-core = { group = "androidx.camera", name = "camera-core", version.ref = "camerax" } |
| 62 | +camera-camera2 = { group = "androidx.camera", name = "camera-camera2", version.ref = "camerax" } |
| 63 | +camera-lifecycle = { group = "androidx.camera", name = "camera-lifecycle", version.ref = "camerax" } |
| 64 | +camera-view = { group = "androidx.camera", name = "camera-view", version.ref = "camerax" } |
| 65 | +``` |
| 66 | + |
| 67 | +5. Navigate to `build.gradle.kts` in your project's `app` directory, then insert the following lines into `dependencies` block. This introduces the above dependencies into the `app` subproject. |
| 68 | + |
| 69 | +```kotlin |
| 70 | + implementation(libs.camera.core) |
| 71 | + implementation(libs.camera.camera2) |
| 72 | + implementation(libs.camera.lifecycle) |
| 73 | + implementation(libs.camera.view) |
| 74 | +``` |
| 75 | + |
| 76 | +## Enable view binding |
| 77 | + |
| 78 | +1. Within the above `build.gradle.kts` file, append the following lines to the end of `android` block to enable view binding feature. |
| 79 | + |
| 80 | +```kotlin |
| 81 | + buildFeatures { |
| 82 | + viewBinding = true |
| 83 | + } |
| 84 | +``` |
| 85 | + |
| 86 | +2. You should be seeing a notification shows up, as shown below. Click **"Sync Now"** to sync your project. |
| 87 | + |
| 88 | + |
| 89 | + |
| 90 | +{{% notice Tip %}} |
| 91 | + |
| 92 | +You may also click the __"Sync Project with Gradle Files"__ button in the toolbar or pressing the corresponding shorcut to start a sync. |
| 93 | + |
| 94 | + |
| 95 | +{{% /notice %}} |
| 96 | + |
| 97 | +3. Navigate to `MainActivity.kt` source file and make following changes. This inflates the layout file into a view binding object and stores it in a member variable within the view controller for easier access later. |
| 98 | + |
| 99 | + |
| 100 | + |
| 101 | +## Configure CameraX preview |
| 102 | + |
| 103 | +1. **Replace** the placeholder "Hello World!" `TextView` within the layout file `activity_main.xml` with a camera preview view: |
| 104 | + |
| 105 | +```xml |
| 106 | + <androidx.camera.view.PreviewView |
| 107 | + android:id="@+id/view_finder" |
| 108 | + android:layout_width="match_parent" |
| 109 | + android:layout_height="match_parent" |
| 110 | + app:scaleType="fillStart" /> |
| 111 | +``` |
| 112 | + |
| 113 | + |
| 114 | +2. Add the following member variables to `MainActivity.kt` to store camera related objects: |
| 115 | + |
| 116 | +```kotlin |
| 117 | + // Camera |
| 118 | + private var camera: Camera? = null |
| 119 | + private var cameraProvider: ProcessCameraProvider? = null |
| 120 | + private var preview: Preview? = null |
| 121 | +``` |
| 122 | + |
| 123 | +3. Add two new private methods named `setupCamera()` and `bindCameraUseCases()` within `MainActivity.kt`: |
| 124 | + |
| 125 | +```kotlin |
| 126 | + private fun setupCamera() { |
| 127 | + viewBinding.viewFinder.post { |
| 128 | + cameraProvider?.unbindAll() |
| 129 | + |
| 130 | + ProcessCameraProvider.getInstance(baseContext).let { |
| 131 | + it.addListener( |
| 132 | + { |
| 133 | + cameraProvider = it.get() |
| 134 | + |
| 135 | + bindCameraUseCases() |
| 136 | + }, |
| 137 | + Dispatchers.Main.asExecutor() |
| 138 | + ) |
| 139 | + } |
| 140 | + } |
| 141 | + } |
| 142 | + |
| 143 | + private fun bindCameraUseCases() { |
| 144 | + // TODO: TO BE IMPLEMENTED |
| 145 | + } |
| 146 | +``` |
| 147 | + |
| 148 | +4. Implement the above `bindCameraUseCases()` method: |
| 149 | + |
| 150 | +```kotlin |
| 151 | +private fun bindCameraUseCases() { |
| 152 | + val cameraProvider = cameraProvider |
| 153 | + ?: throw IllegalStateException("Camera initialization failed.") |
| 154 | + |
| 155 | + val cameraSelector = |
| 156 | + CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_FRONT).build() |
| 157 | + |
| 158 | + // Only using the 4:3 ratio because this is the closest to MediaPipe models |
| 159 | + val resolutionSelector = |
| 160 | + ResolutionSelector.Builder() |
| 161 | + .setAspectRatioStrategy(AspectRatioStrategy.RATIO_4_3_FALLBACK_AUTO_STRATEGY) |
| 162 | + .build() |
| 163 | + val targetRotation = viewBinding.viewFinder.display.rotation |
| 164 | + |
| 165 | + // Preview usecase. |
| 166 | + preview = Preview.Builder() |
| 167 | + .setResolutionSelector(resolutionSelector) |
| 168 | + .setTargetRotation(targetRotation) |
| 169 | + .build() |
| 170 | + |
| 171 | + // Must unbind the use-cases before rebinding them |
| 172 | + cameraProvider.unbindAll() |
| 173 | + |
| 174 | + try { |
| 175 | + // A variable number of use-cases can be passed here - |
| 176 | + // camera provides access to CameraControl & CameraInfo |
| 177 | + camera = cameraProvider.bindToLifecycle( |
| 178 | + this, cameraSelector, preview, |
| 179 | + ) |
| 180 | + |
| 181 | + // Attach the viewfinder's surface provider to preview use case |
| 182 | + preview?.surfaceProvider = viewBinding.viewFinder.surfaceProvider |
| 183 | + } catch (exc: Exception) { |
| 184 | + Log.e(TAG, "Use case binding failed", exc) |
| 185 | + } |
| 186 | + } |
| 187 | +``` |
| 188 | + |
| 189 | +5. Add a [companion object](https://kotlinlang.org/docs/object-declarations.html#companion-objects) to `MainActivity.kt` and declare a `TAG` constant value for `Log` calls to work correctly. This companion object comes handy for us to define all the constants and shared values accessible across the entire class. |
| 190 | + |
| 191 | +```kotlin |
| 192 | + companion object { |
| 193 | + private const val TAG = "MainActivity" |
| 194 | + } |
| 195 | +``` |
| 196 | + |
| 197 | +In the next chapter, we will build and run the app to make sure the camera works well. |
0 commit comments