Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Androidアプリ開発研修【MIXI 23新卒技術研修】

Androidアプリ開発研修【MIXI 23新卒技術研修】

23新卒技術研修で実施したAndroidアプリ開発研修の講義資料です。

動画:https://youtu.be/VfZMuEL2tGk

ハンズオン用リポジトリ:https://github.com/mixigroup/2023AndroidTraining

資料の利用について
公開している資料は勉強会や企業の研修などで自由にご利用頂いて大丈夫ですが、以下の形での利用だけご遠慮ください。
・受講者から参加費や授業料などを集める形での利用(会場費や飲食費など勉強会運営に必要な実費を集めるのは問題ありません)
・出典を削除または改変しての利用

MIXI ENGINEERS

April 26, 2023
Tweet

More Decks by MIXI ENGINEERS

Other Decks in Technology

Transcript

  1. ©MIXI Android 9 API (SDK ) 2008.09 1.0 1 Base

    ... 2017.08 8.0 26 Oreo 2017.12 8.1 27 Oreo MR1 2018.08 9 28 Pie 2019.09 10 29 Q 2020.09 11 30 R 2021.10 12 31 S 2022.03 12L 32 S v2 2022.08 13 33 Tiramisu - 14 34 Upside Down Cake API ( )
  2. ©MIXI 13 Android Studio AAB (Android App Bundle) Google Play

    : : xxhdpi CPU: arm : : hdpi CPU: x86 APK
  3. ©MIXI 14 Android Studio AAB (Android App Bundle) Google Play

    l l : : xxhdpi CPU: arm : : hdpi CPU: x86
  4. ©MIXI Kotlin Android (2017 ) Java 15 Kotlin l :

    l Null : l Java : Kotlin Java Java Kotlin
  5. ©MIXI Kotlin Android (2017 ) Java 16 Kotlin l :

    l Null : l Java : Kotlin Java Java Kotlin NullPointerException var count: Int // null var count: Int? // null
  6. ©MIXI Kotlin Android, iOS, Web https://kotlinlang.org/docs/multiplatform.html 17 Kotlin Multiplatform Kotlin

    UI ( ) https://developer.android.com/jetpack/compose?hl=ja Jetpack Compose
  7. ©MIXI Wear OS, ChromeOS, Android TV, Android Auto API :

    Android 13 API 33, Tiramisu APK : AAB : Google Play APK : , Null , Java Kotlin : Kotlin Multiplatform, Jetpack Compose 18 Android Kotlin
  8. 2

  9. ©MIXI ( ) 1. Android 11 PC 2. https://developer.android.com/studio/debug/dev-options?hl=ja#enable 3.

    Android Studio Pair Devices Using Wi-Fi 4. ON 5. Android Studio 6. Android Studio 26 2. Wi-Fi
  10. ©MIXI : 1. Android Studio Get from VCS 28 2.

    URL [email protected]:mixigroup/2023AndroidTraining.git git clone OK 3. app.ui 1
  11. ©MIXI Jetpack l l 29 app Android 13 Android 12

    Android 11 if else if else ... app Android 13 Android 12 Android 11 Jetpack
  12. ©MIXI Jetpack Compose Jetpack Kotlin UI 2021 UI ( )

    https://developer.android.com/jetpack/compose?hl=ja 30
  13. ©MIXI 31 View : UI Compose : UI val textView

    = TextView(context) textView.setText("Hello") textView.setTextColor(Color.BLUE) Text( text = "Hello", color = Color.Blue ) View UI View Composable UI Composable
  14. ©MIXI 32 View : UI Compose : UI val textView

    = TextView(context) textView.setText("Hello") textView.setTextColor(Color.BLUE) Text( text = "Hello", color = Color.Blue ) Composable UI Composable UI Recompose
  15. ©MIXI Composable 33 @Composable fun RedText( text: String, modifier: Modifier

    = Modifier ) { Text( text = text, modifier = modifier, color = Color.Red ) } RedText( text = "Red text" ) RedText( text = "Red text with blue background", modifier = Modifier.background(Color.Blue) )
  16. ©MIXI Composable 34 @Composable fun RedText( text: String, modifier: Modifier

    = Modifier ) { Text( text = text, modifier = modifier, color = Color.Red ) } Composable Compose Composable l ProfileScreen l LoginButton l HeaderImage
  17. ©MIXI Composable 35 // modifier RedText( text = "Red text"

    ) // modifier RedText( text = "Red text with blue background", modifier = Modifier.background(Color.Blue) ) text = modifier API
  18. ©MIXI Composable 37 @Composable fun Button( ... content: @Composable ()

    -> Unit ) Button(content = { Text(text = "Hello") }) Button() { Text(text = "Hello") } Button { Text(text = "Hello") } ( ) -> () -> Unit { -> } () ()
  19. ©MIXI Modifier.padding(all = 8.dp) Modifier.padding(horizontal = 8.dp, vertical = 4.dp)

    Modifier.padding(start = 8.dp, top = 4.dp, end = 8.dp, bottom = 16.dp) Modifier 38 // 96 x 48 dp Modifier.width(96.dp).height(48.dp) // 48 x 48 dp Modifier.size(48.dp) // Modifier.fillMaxWidth().wrapContentHeight() // Modifier.fillMaxSize() dp
  20. ©MIXI Composable 43 Column( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(8.dp)

    ) { Text(text = "Top") Text(text = "Center") Text(text = "Bottom") } Composable (36 )
  21. ©MIXI Composable Spacer 44 Column(horizontalAlignment = Alignment.CenterHorizontally) { Text(text =

    "Top") Spacer(modifier = Modifier.height(8.dp)) Text(text = "Center") Spacer(modifier = Modifier.height(8.dp)) Text(text = "Bottom") }
  22. ©MIXI 45 Column(modifier = modifier.height(160.dp)) { Text(text = "Top", modifier

    = Modifier.weight(1f)) Text(text = "Center") Text(text = "Bottom") } Column(modifier = modifier.height(160.dp)) { Text(text = "Top", modifier = Modifier.weight(1f)) Text(text = "Center", modifier = Modifier.weight(2f)) Text(text = "Bottom", modifier = Modifier.weight(3f)) } 1:2:3 ⾒
  23. ©MIXI Composable 46 Row( horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically

    ) { Text(text = "Left") Text(text = "Center") Text(text = "Right") } Row : Column
  24. ©MIXI : LazyColumn Compose 47 Column LazyColumn Compose Item 1

    Item 2 Item 3 Item 4 Item 5 Item 6 Item 7 Item 8 Item 1 Item 2 Item 3 Item 4 Item 5 Item 6 Item 7 Item 8 Compose
  25. ©MIXI LazyColumn LazyRow 48 LazyColumn( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement =

    Arrangement.spacedBy(8.dp) ) { // 1 item { Text(text = "Header") } // items(listOf("1", "2", "3")) { text -> Text(text = text) } }
  26. ©MIXI 3.1 app.ui BookItem Android Studio Tips 50 16 dp

    8 dp 8 dp 4 dp 18 sp, 16 sp 16 dp title author
  27. ©MIXI UI 53 Jetpack UI Kotlin Jetpack Compose Composable UI

    Modifier Composable Column , Row , LazyColumn / LazyRow DP , SP
  28. 4

  29. ©MIXI Android 55 ( ) l String resource l Drawable

    resource l Raw resource src/main/res/
  30. ©MIXI res/values/strings.xml 56 <resources> <string name="login"> </string> <string name="settings"> </string>

    ... </resources> Text(text = stringResource(id = R.string.login)) Text(text = stringResource(id = R.string.settings)) UI
  31. ©MIXI Icon( painter = painterResource(id = R.drawable.ramen), contentDescription = stringResource(R.string.ramen),

    tint = Color.Red ) 62 Image( painter = painterResource(id = R.drawable.ramen), contentDescription = stringResource(R.string.ramen) ) Icon
  32. ©MIXI Modifier 63 Image( ... modifier = Modifier.clip(CircleShape) ) Image(

    ... modifier = Modifier.clip(RoundedCornerShape(size = 8.dp)) )
  33. ©MIXI 8dp padding 4.2 BookItem 65 16 dp 16 dp

    48 x 48 dp l Android Studio l Modifier (33 ) l Modifier : https://developer.android.com/jetpack/compose/modifiers?hl=ja#order-modifier-matters
  34. 5

  35. ©MIXI 1 : 71 var count = 0 Button(onClick =

    { count++ }) { Text(text = count.toString()) } count Recompose val : var : 0 0
  36. ©MIXI 1 : 72 var count by remember { mutableStateOf(0)

    } Button(onClick = { count++ }) { Text(text = count.toString()) } MutableState Compose Recompose (UI )
  37. ©MIXI 1 : 73 var count by remember { mutableStateOf(0)

    } Button(onClick = { count++ }) { Text(text = count.toString()) } Delegated property MutableState<Int> Int remember Recompose API : https://developer.android.com/jetpack/compose/state#state-in-composables
  38. ©MIXI 1 : 74 var count by remember { mutableStateOf(0)

    } Button(onClick = { count++ }) { Text(text = count.toString()) } 0 1
  39. ©MIXI var text by remember { mutableStateOf("") } TextField( value

    = text, onValueChange = { value -> text = value } ) 2 : 75
  40. ©MIXI 78 Composable (Recompose) UI MutableState Recompose var state by

    remember { mutableStateOf( ) } UI Composable onClick onValueChange e.g. Button, TextField, Switch UI UI
  41. 6

  42. ©MIXI 84 Log.v(TAG, " ") Log.d(TAG, " ") Log.i(TAG, "

    ") Log.w(TAG, " ") Log.e(TAG, " ") ( )
  43. 7

  44. ©MIXI UI 90 UI UI Compose l UI l UI

    ViewModel l UI l UI UI
  45. ©MIXI : ver. 93 Column(...) { var result by remember

    { mutableStateOf("") } Text(text = result) Button(onClick = { result = listOf(" ", " ", ...).random() }) { Text(text = stringResource(R.string.pick_omikuji)) } }
  46. ©MIXI ( ) 95 class OmikujiRepository { fun getResult(): String

    { return listOf(" 規", "規", ...).random() } }
  47. ©MIXI ViewModel 98 class OmikujiViewModel( private val repository: OmikujiRepository =

    OmikujiRepository() ) : ViewModel() { ... } (DI) Hilt DI
  48. ©MIXI UiState MutableState 99 class OmikujiViewModel(...) : ViewModel() { var

    uiState by mutableStateOf(OmikujiUiState()) private set } MutableStateFlow l MutableState Compose API l MutableStateFlow Kotlin API
  49. ©MIXI UiState ViewModel 100 class OmikujiViewModel(...) : ViewModel() { ...

    fun pick() { val result = repository.getResult() uiState = uiState.copy(result = result) } } UiState UI
  50. ©MIXI UI ViewModel 101 val viewModel: OmikujiViewModel = viewModel() val

    uiState = viewModel.uiState Text(text = uiState.result) var result by remember { mutableStateOf("") } Text(text = result) Before After Compose ViewModel
  51. ©MIXI Button(onClick = viewModel::pick) UI ViewModel 102 Button(onClick = {

    viewModel.pick() }) Button(onClick = { result = listOf(" ", " ", ...).random() }) Before After OK
  52. 8

  53. ©MIXI ViewModel 111 class OmikujiViewModelTest { private val viewModel =

    OmikujiViewModel() @Test fun testPick() { assertEquals("", viewModel.uiState.result) viewModel.pick() assertNotEquals("", viewModel.uiState.result) } }
  54. ©MIXI ViewModel 112 class OmikujiViewModelTest { private val viewModel =

    OmikujiViewModel() @Test fun testPick() { assertEquals("", viewModel.uiState.result) viewModel.pick() assertNotEquals("", viewModel.uiState.result) } } JUnit 4 (Java ) API
  55. ©MIXI ViewModel 113 class OmikujiViewModelTest { private val viewModel =

    OmikujiViewModel() @Test fun testPick() { assertEquals("", viewModel.uiState.result) viewModel.pick() assertNotEquals("", viewModel.uiState.result) } } uiState
  56. ©MIXI ViewModel 114 class OmikujiViewModelTest { private val viewModel =

    OmikujiViewModel() @Test fun testPick() { assertEquals("", viewModel.uiState.result) viewModel.pick() assertEquals(" ", viewModel.uiState.result) } }
  57. ©MIXI 115 class OmikujiViewModel( private val repository: OmikujiRepository ) :

    ViewModel() DefaultOmikujiRepository FakeOmikujiRepository
  58. ©MIXI 116 interface OmikujiRepository { fun getResult(): String } class

    DefaultOmikujiRepository : OmikujiRepository { override fun getResult(): String { return listOf(" ", " ", ...).random() } } class FakeOmikujiRepository : OmikujiRepository { override fun getResult(): String { return " " } }
  59. ©MIXI 117 class OmikujiViewModelTest { private val viewModel = OmikujiViewModel(FakeOmikujiRepository())

    @Test fun testPick() { assertEquals("", viewModel.uiState.result) viewModel.pick() assertEquals(" ", viewModel.uiState.result) } }
  60. 9

  61. ©MIXI 124 data class WeatherUiState( val weather: String = "",

    val isLoading: Boolean = false ) fun getWeather() { // uiState = uiState.copy(isLoading = true) // val weather = repository.getWeather() // uiState = uiState.copy(weather = weather, isLoading = false) } ViewModel
  62. ©MIXI 125 data class WeatherUiState( val weather: String = "",

    val isLoading: Boolean = false ) fun getWeather() { // uiState = uiState.copy(isLoading = true) // val weather = repository.getWeather() // uiState = uiState.copy(weather = weather, isLoading = false) } ViewModel (UI )
  63. ©MIXI Kotlin 126 viewModelScope.launch { uiState = uiState.copy(isLoading = true)

    val weather = repository.getWeather() uiState = uiState.copy(weather = weather, isLoading = false) } uiState = uiState.copy(isLoading = true) val weather = repository.getWeather() uiState = uiState.copy(weather = weather, isLoading = false)
  64. ©MIXI Kotlin 127 viewModelScope.launch { uiState = uiState.copy(isLoading = true)

    val weather = repository.getWeather() uiState = uiState.copy(weather = weather, isLoading = false) } getWeather()
  65. ©MIXI suspend 130 Kotlin API delay(timeMillis = 2000) 2 Ktor

    (HTTP ) API val response = client.request(urlString = "https://...") DB Jetpack Room (SQLite ) API val user = dao.getUser(userId = ...)
  66. ©MIXI 132 UI l l suspend l suspend l suspend

    l suspend suspend e.g. , DB
  67. 10

  68. ©MIXI 135 data class WeatherUiState( val weather: String = "",

    val isLoading: Boolean = false, val isError: Boolean = false ) sealed class WeatherUiState { data class Success(val weather: String) : WeatherUiState() object Loading : WeatherUiState() object Error : WeatherUiState() } OK UiState
  69. ©MIXI 136 sealed class WeatherUiState { data class Success(val weather:

    String) : WeatherUiState() object Loading : WeatherUiState() object Error : WeatherUiState() } sealed class WeatherUiState WeatherUiState
  70. ©MIXI try-catch 137 fun getWeather() { viewModelScope.launch { uiState =

    WeatherUiState.Loading try { val weather = repository.getWeather() uiState = WeatherUiState.Success(weather = weather) } catch (error: SomeException) { uiState = WeatherUiState.Error } } }
  71. ©MIXI try-catch 138 fun getWeather() { viewModelScope.launch { uiState =

    WeatherUiState.Loading try { val weather = repository.getWeather() uiState = WeatherUiState.Success(weather = weather) } catch (error: SomeException) { uiState = WeatherUiState.Error } } } catch Exception catch throw ( )
  72. ©MIXI UI when app.weather 139 @Composable fun WeatherScreen(...) { val

    uiState = viewModel.uiState when (uiState) { is WeatherUiState.Success -> SuccessScreen(...) is WeatherUiState.Loading -> LoadingScreen(...) is WeatherUiState.Error -> ErrorScreen(...) } }
  73. ©MIXI Compose, , , , https://github.com/android/nowinandroid 146 https://github.com/android/architecture-samples Compose UI

    https://github.com/android/compose-samples Now in Android App Android Architecture Samples Jetpack Compose Samples