[android] データの非同期処理(Coroutines / Flow)

MainViewModel.kt

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch

class MainViewModel : ViewModel() {
    private val _counter = MutableStateFlow(0)
    val counter: StateFlow<Int> = _counter

    fun startCounting() {
        viewModelScope.launch {
            for (i in 1..10) {
                delay(1000) // 1秒待つ
                _counter.value = i
            }
        }
    }
}
package com.example.myapplicationcoroutine

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import android.widget.Button
import android.widget.TextView
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import com.example.myapplicationcoroutine.ui.theme.MyApplicationCoroutineTheme

class MainActivity : ComponentActivity() {

    private val viewModel: MainViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val counterTextView = findViewById<TextView>(R.id.counterTextView)
        val startButton = findViewById<Button>(R.id.startButton)

        // ボタンを押すとカウントスタート
        startButton.setOnClickListener {
            viewModel.startCounting()
        }

        // Flowを監視してUI更新
        lifecycleScope.launch {
            viewModel.counter.collectLatest { value ->
                counterTextView.text = "カウント: $value"
            }
        }
    }
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:gravity="center"
    android:padding="16dp">

    <TextView
        android:id="@+id/counterTextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="カウント: 0"
        android:textSize="24sp"
        android:layout_marginBottom="24dp"/>

    <Button
        android:id="@+id/startButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="カウント開始"/>
</LinearLayout>

Android: Repositoryパターン RetrofitやRoomとの接続を分離して保守性UP

class UserViewModel (private val repository: UserRepository) : ViewModel() {

    val users: LiveData<List<User>> = repository.users.asLiveData()

    fun refreshUsers() {
        val users: LiveData<List<User>> = repository.asLiveData()

        fun refreshUsers() {
            viewModelScope.launch {
                repository.fetchUsersFromApi()
            }
        }
    }
}
class UserRepository(
    private val apiService: ApiService,
    private val userDao: UserDao
) {
    val users: Flow<List<User>> = userDao.getAllUsers()

    suspend fun fetchUsersFromApi() {
        try {
            val usersFromApi = apiService.getUsers()
            userDao.insertUsers(usersFromApi)
        } catch(e: Exception) {
            // error handling
        }
    }
}
class UserRepository(
    private val apiService: ApiService,
    private val userDao: UserDao
) {
    val users: Flow<List<User>> = userDao.getAllUsers()

    suspend fun fetchUsersFromApi() {
        try {
            val usersFromApi = apiService.getUsers()
            userDao.insertUsers(usersFromApi)
        } catch(e: Exception) {
            // error handling
        }
    }
}

Android: ViewModel状態管理サンプル

model/User.kt

package com.example.myapplicationstate.model

data class User {
    val id: Int,
    val name: String
}

model/UiState.kt

package com.example.myapplicationstate.model

sealed class UiState {
    object Loading: UiState()
    data class Success(val users: List<User>): UiState()
    data class Error(val message: String): UiState()
}

種類 主な目的 特徴
sealed class 型の制限つき継承(状態分岐) 継承を制限し、when式で exhaustiveness(漏れなくチェック)できる
data class データ保持専用クラス 自動で toString, equals, copy などが生成される

package com.example.myapplicationstate.viewmodel

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch

class UserViewModel : ViewModel(){
    private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
    val uiState: StateFlow<UiState> = _uiSate

    init {
        fetchUsers()
    }

    fun fetchUsers(){
        viewModelScope.launch {
            _uiState.value = UiState.Loading
            delay(2000)

            val  result = runCatching {
                getUsersFromApi()
            }

            _uiState.value =result.fold (
                onSuccess={ UiState.Success(it) },
                onFailure = { UiState.Error(it.message ?: "Unknown Error") }
            )
        }
    }

    private suspend fun getUsersFromApi(): List<User> {
        // 成功/失敗を切り替えるための仮コード
        if ((0..1).random() == 0) {
            throw RuntimeException("通信エラー")
        }

        return listOf(
            User(1, "Alice"),
            User(2, "Bob"),
            User(3, "Charlie")
        )
    }
}

ui/UserScreen.kt

package com.example.myapplicationstate.ui.screen
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.material.*
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import model.UiState

@Composable
fun UserScreen(userViewModel: UserViewModel = viewModel()) {
    val uiState by userViewModel.uiState.collectAsState()

    when (uiState){
        is UiState.Loading -> {
            Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
                CircularProgressIndicator()
            }
        }

        is UiState.Success -> {
            val users = (uiState as UiState.Success).users
            LazyColumn {
                items(users) { user ->
                    Text(
                        text = user.name,
                        modifier = Modifier
                            .fillMaxWidth()
                            .padding(16.dp)
                    )
                }
            }
        }

        is UiState.Error -> {
            val message = (uiState as UiSatete.Error).message
            Column (
                modifier = Modifier.fillMaxSize(),
                verticalArrangement = Arrangement.Center,
                horizontalAlignment = Alignment.CenterHorizontally
            ) {
                Text("エラー: $message", color = MaterialTheme.colors.error)
                Spacer(modifier = Modifier.height(16.dp))
                Button(onClick = { userViewModel.fetchUsers() }) {
                    Text("リトライ")
                }
            }
        }
    }
}

## @Composable

@Composable
fun Greeting(name: String) {
    Text(text = "Hello, $name!")
}
@Composable
fun MyScreen() {
    Column {
        Text("こんにちは")
        Button(onClick = { /* クリック処理 */ }) {
            Text("ボタン")
        }
    }
}

MainActivityはアプリの最初の画面
アプリのエントリーポイント

androidではuiとロジックが分けられるMVVMが一般的

Jetpack Compose + ViewMode

build.gradle.kts

plugins {
    alias(libs.plugins.android.application)
    alias(libs.plugins.kotlin.android)
    alias(libs.plugins.kotlin.compose)
}

android {
    namespace = "com.example.myapplicationstate"
    compileSdk = 36

    defaultConfig {
        applicationId = "com.example.myapplicationstate"
        minSdk = 24
        targetSdk = 36
        versionCode = 1
        versionName = "1.0"

        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            isMinifyEnabled = false
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
    }
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_11
        targetCompatibility = JavaVersion.VERSION_11
    }
    kotlinOptions {
        jvmTarget = "11"
    }
    buildFeatures {
        compose = true
    }
}

dependencies {

    implementation(libs.androidx.core.ktx)
    implementation(libs.androidx.lifecycle.runtime.ktx)
    implementation(libs.androidx.activity.compose)
    implementation(platform(libs.androidx.compose.bom))
    implementation(libs.androidx.ui)
    implementation(libs.androidx.ui.graphics)
    implementation(libs.androidx.ui.tooling.preview)
    implementation(libs.androidx.material3)
    testImplementation(libs.junit)
    androidTestImplementation(libs.androidx.junit)
    androidTestImplementation(libs.androidx.espresso.core)
    androidTestImplementation(platform(libs.androidx.compose.bom))
    androidTestImplementation(libs.androidx.ui.test.junit4)

    implementation(libs.androidx.lifecycle.viewmodel.compose)
    debugImplementation(libs.androidx.ui.tooling)
    debugImplementation(libs.androidx.ui.test.manifest)
}

MainActivity.kt

package com.example.myapplicationstate

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.lifecycle.viewmodel.compose.viewModel
import com.example.myapplicationstate.ui.CounterScreen

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MaterialTheme {
                Surface {
                    CounterScreen()
                }
            }
        }
    }
}

MainViewModel.kt

package com.example.myapplicationstate.ui.theme

import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.example.myapplicationstate.MainViewModel

@Composable
fun CounterScreen(viewModel: MainViewModel = viewModel()) {
    val count by viewModel.count.collectAsState()

    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(32.dp),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center
    ) {
        Text(text = "カウント: $count", style = MaterialTheme.typography.headlineMedium)
 

Androidのstate

XMLレイアウト + Retrofit通信

activity_main.xml

<TextView
    android:id="@+id/counterText"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="カウント: 0"
    android:textSize="20sp"
    android:layout_marginTop="16dp" />

<Button
    android:id="@+id/incrementButton"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="カウントアップ" />

MainActivity

package com.example.myapplication

import com.example.myapplication.ChatApi
import com.example.myapplication.ChatRequest
import com.example.myapplication.ChatResponse
import com.example.myapplication.ApiClient


import android.os.Bundle
import android.widget.Button
import android.widget.EditText
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response

class MainActivity : AppCompatActivity() {

    private var count = 0

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val inputMessage = findViewById<EditText>(R.id.inputMessage)
        val sendButton = findViewById<Button>(R.id.sendButton)
        val resultText = findViewById<TextView>(R.id.resultText)

        val counterText = findViewById<TextView>(R.id.counterText)
        val incrementButton = findViewById<Button>(R.id.incrementButton)

        sendButton.setOnClickListener {
            val message = inputMessage.text.toString()
            val request = ChatRequest(name = "田中", job = "エンジニア")

            ApiClient.chatApi.sendMessage(request).enqueue(object : Callback<ChatResponse> {
                override fun onResponse(call: Call<ChatResponse>, response: Response<ChatResponse>) {
                    if (response.isSuccessful) {
                        val body = response.body()
                        resultText.text = if (body != null) {
                            "name: ${body.name}\njob: ${body.job}\nid: ${body.id}\ncreatedAt: ${body.createdAt}"
                        } else {
                            "応答なし"
                        }
                    } else {
                        resultText.text = "エラーコード: ${response.code()}"
                    }
                }

                override fun onFailure(call: Call<ChatResponse>, t: Throwable) {
                    resultText.text = "通信失敗: ${t.localizedMessage}"
                }
            })
        }
        
        incrementButton.setOnClickListener {
            count++
            counterText.text = "カウント: $count"
        }
    }
}

特徴 XMLレイアウト + Retrofit通信 Jetpack Compose
UIの作り方 XMLファイルで定義 Kotlinコードで定義
表示の変更 TextView.setText()などで明示的に更新 状態が変われば自動で再描画される
直感性 複雑で古いUIも対応できるが手間 シンプルでモダンなUIが書きやすい
可読性・保守性 XMLとKotlinが分かれて見づらくなる すべてKotlinに書けるので読みやすい
プレビュー Android Studioでリアルタイムプレビュー可能 同じく可(Compose Preview)

モダンな描き方はjetpack composeが良さそう

Androidの次の学習項目

⭐️ 高 ViewModel + State管理 複雑なUI状態を安全に管理。画面回転にも強い。
⭐️ 高 Repositoryパターン RetrofitやRoomとの接続を分離して保守性UP
⭐️ 高 Room(ローカルDB) オフライン保存・データ永続化の基礎
⭐️ 高 データの非同期処理(Coroutines / Flow) 通信やDB操作を効率的に行う。非同期処理の本命
⭐️ 中 UIのアニメーション Composeのanimate*関数で滑らかなUI体験
⭐️ 中 Dependency Injection(Hilt) テストしやすい、保守しやすい設計にするための基盤
⭐️ 中 Jetpack Compose Navigation(複雑版) 引数付き遷移、戻る処理、BottomNavなどを扱う
⭐️ 低〜中 テスト(Unit, UIテスト) 安定したアプリを作るには重要。ただし最初は後回しでもOK
⭐️ 低 デザインパターン(MVVMなど) 設計力を高めたいときに学習

いまいちピンとこないものが多いですが、結構ありますね。

Kotlin _4

### ジェネリクス
型を汎用化する
class使用時にデータ型を指定する

class MyData<T> {
    fun getThree(x: T){
        println(x)
        println(x)
        println(x)
    }
}

fun main(args: Array<String>) {
	val mi = MyData<Int>()
    mi.getThree(55)
}

### data class

data class Point(val x: Int, val y: Int)

fun main(args: Array<String>) {
	val p1 = Point(3, 5)
    val p2 = Point(3, 5)
    
    println(p1)
    println(if (p1 == p2) "same" else "not same")
}

### Collection
List: 順番を持つデータの集合(Immutable/Mutable)、Set: 順番を持たない|重複を許さないデータの集合(Immutable/Mutable)、Mapはkeyと値でデータを管理(Immutable/Mutable)
– Immutableなデータを作る
– listはlistOfで使う。変更する場合は、mutableListOfで使用する

fun main(args: Array<String>) {
	val sales: List<Int> = listOf(20, 30, 40)
    println(sales[1])
}

– setはsetOf
– 変更する場合は、mutableSetOfで使用する

fun main(args: Array<String>) {
	val answers: Set<Int> = setOf(5, 3, 8, 5)
    println(answers)
    println(answers.contains(3))
    
    val set1 = setOf(1, 3, 4, 8)
    val set2 = setOf(3, 5, 7, 9)
    println(set1.intersect(set2))
    println(set1.union(set2))
}

– mapはmapOf
– mutableMap

fun main(args: Array<String>) {
	val users: Map<String, Int> = mapOf("yamada" to 10, "tanaka" to 20, "sato" to 30)
    println(users["yamada"])
    println(users.size)
	println(users.keys)
    println(users.values)
    println(users.entries)
}

### map
コレクションを処理するための命令にmap, filter, forEachなどが使える

fun main(args: Array<String>) {
	val prices = listOf(53.2, 48.2, 32.8)
    prices
    	.map{ n -> n * 1.08} // 引数 ->処理
        .filter { n -> n > 50}
        .forEach { println(it)}
}

### 例外処理
try & catch

class MyException(message: String): Throwable(message){
    
}

fun div(a: Int, b: Int){
    try {
        if(b < 0){
            throw MyException("not minus!")
        }
        println(a / b)
    } catch(e: ArithmeticException){
        println(e.message)
    }
}

fun main(args: Array<String>) {
	div(3, 0)
}

### Nullable
nullになりそうな型は、型の後ろに「?」を付ける

fun main(args: Array<String>) {
	val s: String = null
    println(s)
}

Null can not be a value of a non-null type String

fun main(args: Array<String>) {
	val s: String? = null
    println(s)
    
    if(s != null){
        println(s.length)
    } else {
        println(null)
    }
}

Kotlin _3

### class

class User {
    var name = "Me!"
    fun sayHi(){
        println("hi $name")
    }
}

fun main(args: Array<String>){
	val user = User()
    println(user.name)
    user.sayHi()
    
    user.name = "steve"
    println(user.name)
    user.sayHi()  
}

constructor
->classの引数の渡し方はmethodと同様に型を指定する

class User(var name: String) { // constructor
    var team = "red"
    init {
        println("instance created: name: $name, team: $team")
    }
    fun sayHi(){
        println("hi $name")
    }
}

fun main(args: Array<String>){
	val tom = User("tom")
    println(tom.name)
    tom.sayHi()
}

instance created: name: tom, team: red
tom
hi tom

### getter, setter

    // getter
//     get(){
//         return field.toUpperCase()
//     }
    get() = field.toUpperCase()
    // setter
    set(value) {
        if(value != ""){
            field = value
        }
    }

### override
継承される側はopenを付ける

class AdminUser(name: String): User(name){
    fun sayHello(){
        println("hello $name")
    }
    override fun sayHi(){
        println("[admin] hi $name")
    }
    
}

open class User(var name: String){
    open fun sayHi(){
        println("hi $name")
    }
}

fun main(args: Array<String>) {
    val bob = AdminUser("bob")
    println(bob.name)
    bob.sayHello()
    bob.sayHi()
}

### アクセス修飾子
public:どこからでも、protected: そのクラス+サブクラス、private:そのクラスのみ
-> アクセスをcontrollすることで安全なプログラムを書ける

### 拡張

fun User.sayHi(){
    println("[ext] hello $name")
}

val User.myName: String
 get() = "I am $name"

open class User(var name: String){
    fun sayHi(){
        println("hi $name")
    }
}

fun main(args: Array<String>) {
    val bob = User("bob")
    println(bob.name)
    bob.sayHi()
}

### 抽象クラス・具象クラス
抽象クラスはabstractとする

abstract class User{
    abstract fun sayHi()
}

class Japanese: User() {
    override fun sayHi(){
        println("こんにちは!")
    }
}

class American: User() {
    override fun sayHi(){
        println("Hi!")
    }
}


fun main(args: Array<String>) {
	val tom = American()
    val aki = Japanese()
    tom.sayHi()
    aki.sayHi()
}

### Interface
抽象プロパティ、抽象メソッド、メソッド

interface Sharable {
    val version: Double
    fun share()
    fun getInfo(){
        println("Share I/F ($version)")
    }
}

class User: Sharable {
    override val version = 1.1
    override fun share(){
        println("Sharing ...")
    }
}

fun main(args: Array<String>) {
	val user = User()
    user.share()
    user.getInfo()
}

Kotlin _2

### 演算

fun main(args: Array<String>){
	// + - * / %
	val x = 10
    println(x / 3)
    println(x / 3.0)
    println(x % 3.0)
    
    var y = 5
    y++
    println(y)
    y--
    println(y)
    
    var z = 4
    z += 12
    
    // AND &&, or ||, Not !
    val flag = true
    println(!flag)
}

3
3.3333333333333335
1.0
6
5
false

### 文字列

fun main(args: Array<String>){
	// 文字列
	println("hello " + "world")
    
    val name = "yamada"
	println("my name is $name")
    println("my score is ${15 + 33}")
    
    // \n:改行 \t:タブ
    println("hello\n wor\tld")
}

hello world
my name is yamada
my score is 48
hello
wor ld

### ifの条件分岐

val score = 85
    if(score > 80){
        println("great!")
    } else if(score > 60){
        println("ok")
    } else {
        println("soso ..")
    }

> >= < <= == != 三項演算子 [code] val result = if(score> 80) “great” else “soso”
println(result)
[/code]

### when

// when
	val num = 3
    when(num) {
        0 -> println("Zero")
        1 -> println("One")
        2, 3 -> println("Two or Three")
        in 4..10 -> println("Many")
        else -> println("other")
    }

### while

var i = 0
    while (i < 10){
        println("loop: $i")
        i++
    }

do {
        println("loop2: $i")
        i++
    } while(i < 5)
&#91;/code&#93;

### for
&#91;code&#93;
for (i in 0..9){
        if(i == 5) break
        println(i)
    }

for (i in 0..9){
        if(i == 5) continue
        println(i)
    }
&#91;/code&#93;

### 関数
関数の引数は型を指定する
&#91;code&#93;
fun sayHi(name: String = "John", age: Int = 23){
    println("hi! $name($age)")
}

fun main(args: Array<String>){
	// for (変数)
    sayHi("tom", 22)
    sayHi()
}

値を返す時は、関数側で型を指定する

fun sayHi(): String{
    return "hi!"
}

fun main(args: Array<String>){
    val msg = sayHi()
    println(msg)
}

fun sayHi() = "hi!"

-> 配列で返す場合はどうするんだろう??

Kotlin _1

kotlin
ScalaやGroovyなどの言語と同様に、Javaバイトコードにコンパイルされて、JVMで動作する静的型付けのオブジェクト指向
静的なnull安全が保証されている、NullPointerExceptionを防ぐため、@Nullable 型と @NonNull 型が組み込まれている
Javaよりコードが短く簡潔で、Javaのコードを呼び出せる
Uber、Evernote、NetflixなどがKotlinを使用

### 1. Hello World

fun main(args: Array<String>){
    println("Hello world")
}

### 2. 変数

// -val 再代入できない 
// -var 再代入できる
// なるべくvalで宣言し、必要なところのみvarで宣言する

fun main(args: Array<String>){
    val msg: String = "Hello world"
    println(msg)
}

再代入はできない

fun main(args: Array<String>){
    val msg: String = "Hello world"
    println(msg)
    msg = "Hello world 2"
    println(msg)
}

Val cannot be reassigned
再代入の場合はvarを使う

fun main(args: Array<String>){
    var msg: String = "Hello world"
    println(msg)
    msg = "Hello world 2"
    println(msg)
}

### 3. 基本型
Double: 64ビット浮動小数点
Float: 32ビット浮動小数点
Long: 64ビット符号付き整数
Int: 32ビット符号付き整数
Short: 16ビット符号付き整数
Byte: 8ビット符号付き隻数
Char: 1文字を表す文字列
Boolean: 真偽値
String: 文字列

数値定数

val digits = 1234 // 10進数
val longInt = 1234L // Long
val hex = 0x1F // 16進数
val bin = 0b00001011 // 2進数
val dbl = 1234.5 // デフォルトはdouble
val withE = 123.4e10 // 123.4 * 10^10
val flt = 123.4f // Float

文字と文字列

// Char型はシングルクォート
val c = '0'
val n = '\n'
val u = '\uFF00'

// ダブルクォートの文字列も使える
val s = "Hello, world!\n"

val text = """
文字列を記入する。
	インデントもそのまま。
"""

// 文字列の中に変数を埋め込む
val i = 10
val str = "i = $i, i x 2 = ${i * 2}"

// stringはimmutableで構成要素はchar
val c0 = str[0]
for (chr in str){
	println(chr)
}

fun main(args: Array<String>){
    val msg: String = "hello world"
    val c: Char = 'a'
    
    val i: Int = 100
    val l: Long = 55555555555555L
    
    val d: Double = 234.523 
    val f: Float = 12.344F
    
    val flag: Boolean = true 
}

kotolin 文字列の連結

fun changeTextView(view: View){
        var calendar = Calendar.getInstance()
        val hour = calendar.get(Calendar.HOUR_OF_DAY)
        val minute = calendar.get(Calendar.MINUTE)

        messageTextView.text = "Hello android!"
        textView.text = "時間 ${hour.toString()}:${minute.toString()}"
    }