[android]UIのアニメーション Composeのanimate*関数で滑らかなUI体験

MainActivity.kt は「アプリの起動エントリポイント(=最初に表示される画面)」を定義する場所
UI(画面の中身)はComposable関数として別ファイルに分けて定義 するのが一般的

package com.example.zeus

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            AnimatedBoxDemo()
        }
    }
}

@Composable
fun AnimatedBoxDemo() {
    var expanded by remember { mutableStateOf(false) }

    val boxSize by animateDpAsState(
        targetValue = if (expanded) 200.dp else 100.dp,
        label = "box animation"
    )

    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center
    ) {
        Box(
            modifier = Modifier
                .size(boxSize)
                .background(Color(0xFF4CAF50))
        )

        Spacer(modifier = Modifier.height(24.dp))

        Button(onClick = { expanded = !expanded }) {
            Text(if (expanded) "縮小" else "拡大")
        }
    }
}

byはプロパティデリゲーション(Property Delegation)というkotlin独特の記法で任せるという書き方。

[android] bearer tokenの使いたい

/data/api/ApiService.kt

interface ApiService {
    @GET("user/profile")
    suspend fun getUserProfile(): Response<UserProfile>
}

data/api/AuthInterceptor.kt

class AuthInterceptor(private val tokenProvider: ()-> String?):Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        val token = tokenProvider()
        val request = chain.request().newBuilder().apply {
            token?.let {
                addHeader("Authorization", "Bearer $it")
            }
        }.build()
        return chain.proceed(request)
    }
}

data/api/ApiClient.kt

object ApiClient {
    fun create(tokenProvider: () -> String?): ApiService {
        val client = OkHttpClient.Builder()
            .addInterceptor(AuthInterceptor(tokenProvider))
            .build()

        return Retrofit.Builder()
            .baseUrl("https://your-api.com/") // 実際のURLに変更
            .client(client)
            .addConverterFactory(GsonConverterFactory.create())
            .build()
            .create(ApiService::class.java)
    }
}

presentation/viewmodel/MyViewModel.kt

class MyViewModel : ViewModel() {
    private val token = ""

    private val apiService = ApiClient.create {token}

    var userProfile by mutableStateOf<UserProfile?>(null)
        private set

    fun loadUserProfile() {
        viewModelScope.launch {
            try {
                val response = apiService.getUserProfile()
                if (response.isSuccessful) {
                    userProfile = response.body()
                } else {
                    // エラー処理
                }
            } catch (e: Exception) {
                // 通信エラーなど
            }
        }
    }

}

presentation/screen/ProfileScreen.kt

@Composable
fun ProfileScreen(viewModel: MyViewModel = viewModel()) {
    val profile = viewModel.userProfile

    LaunchedEffect(Unit) {
        viewModel.loadUserProfile()
    }

    profile?.let {
        Text("ユーザー名: ${it.name}")
    } ?: Text("読み込み中...")
}

domain/model/UserProfile.kt

data class UserProfile(
    val id: Int,
    val name: String,
    val email: String
)

[android]Composeのanimate関数

class MainActivity : ComponentActivity() {

    overrider fun onCreate(savedInstanceState: Bundle?){
        super.onCreate(saveInstanceState)
        setContent {
            MaterialTheme {
                AnimatedBoxExample()
            }
        }
    }

    @Composable
    fun AnimatedBoxExample() {
        var expaned by remember {mutableStateOf(false)}

        val boxSize by animateDpAsState(
            targetValue = if(expanded) 200.dp else 100.dp,
            label ="BoxSizeAnimation"
        )

        Column(
            modifier = Modifier
                .fillMaxSize()
                .padding(32.dp),
            verticalArrangement = Arrangement.Center,
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            Box(
                modifier = Modifier
                    .size(boxSize)
                    .padding(8.dp),
                contentAlignment = Alignment.Center
            ) {
                color = MaterialTheme.colorScheme.primary,
                modifier = Modifier.fillMaxSize()
            } {}
        }
        Spacer(modifier = Modifier.height(16.dp))

        Button(onClick= {expanded = !expanded}) {
            Text(text =if (expanded) "縮小する" else "拡大する")
        }
    }
}

animateDpAsState:Dp 値を滑らかにアニメーションさせます。
targetValue:アニメーションで変化させたい目標値。
expanded を切り替えることで、Boxのサイズが 100.dp ↔ 200.dp にアニメーション付きで変化。

WebViewとは?

Webviewとは?
-> HTMLコンテンツをアプリ内で見れるようにすること
-> SafariやChromeで見られる枠組みをアプリ内に作ることができる
-> HTMLの更新をすれば良いので、アプリストアへの申請や審査、アップデートは必要ない
-> iOS, AndroidともにWebviewに対応している
-> ユーザ体験、操作性はネイティブアプリの方が優れている
-> 全てをWebviewにするのではなく、ヘッダ、フッタの調整などを行うことが多い
-> Webブラウザのレンダリングエンジンを呼び出しているが、ブラウザ自体を起動している訳ではない。そのため、設定やCookieなどは別で管理される

アプリ(Webview)のメリット・デメリット
– push通知を送信できる
– ネイティブを開発するよりも開発・運用コストを抑制できる
– Webとの違いを出しずらい

### Android
– Android System Webview
1.GUIコンポーネントのWebviewを使用する

<WebView
android:id=”@+id/webView″
android:layout_width=”match_parent”
android:layout_height=”match_parent”
android:layout_alignParentLeft=”true” />

2. マニュフェストにインターネットアクセスを許可するパーミッション追加
3. インクをクリックしたときに標準ブラウザを起動しないようにする

WebView myWebView = (WebView) findViewById(R.id.webView);
myWebView.setWebViewClient(new WebViewClient());
myWebView.loadUrl(“https://www.google.com/”);

### iOS
– SFSafariView、WKWebView、(UIWebView(非推奨))などを使う
UIApplication classを使う

import UIKit
 
class ViewController: UIViewController , UIWebViewDelegate {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let url = URL(string: "https://hoge.com/")!
        
        if #available(iOS 10.0, *) {
            UIApplication.shared.open(url, options: [:], completionHandler: nil)
        } else {
            UIApplication.shared.openURL(url)
        }
        
    }
}

SFSafariViewController

import UIKit
import SafariServices
 
class ViewController: UIViewController {
 
    override func viewDidLoad() {
        super.viewDidLoad()
 
    }
 
    override func viewDidAppear(_ animated: Bool) {
        let webPage = "https://hoge.com/"
        let safariVC = SFSafariViewController(url: NSURL(string: webPage)! as URL)
        present(safariVC, animated: true, completion: nil)
    }
}

Androidのアーキテクチャ

AndroidのカーネルもLinuxなのね。
アプリケーションフレームワーク、標準フレームワークなどは様々な技術が使用されている

https://android.googlesource.com/

あれ、なんだgoogle gitって?
こちらでやられてるんだ。

Cleanup

Open storage area
– Delete unnecessary files that waste space and free up space for what you need.

– Delete unnecessary file such as cache, temporary files, residual data from your Android device or app
– See which apps occupy the most space
– Identify and delete apps you no longer use

Crash Report

What is a crash report?

When an error occurs in the application, it may be terminated, but the terminal stores the information inside the terminated application at that time.

The screen will be displayed at the end, but if you tap the report, the development team can use the information for defect correction.

The crash report contains the following information, but does not contain personally identifying information.
– App version
– Android version
– Device type
– Error occurrence time
– Error occurrence point
– Number of occurrences
– Message you entered

ドコモの「アプリクラッシュポート」についてを見てみましょう
アプリクラッシュレポート

Certificate and Key Store

A public key certificate, also called an electronic or identity certificate, contains a public key consisting of a public / private key pair, as well as other metadata (such as name and location) that identify the owner of the key. The certificate owner also owns the corresponding private key.

When you sign the APK, the signing tool attaches a public key certificate to the APK. The same is true if you signed the app bundle. A public key certificate acts as “fingerprint” that uniquely associates an APK or app bundle with the owner and the corresponding private key. This will allow Android to verify that subsequent app updates are genuine and have been released by the original author. The key used to create this certificate is called the app signing key.

A keystore is a binary file that contains one or more private keys. In order to allow users to install new versions as app updates, all apps must use the same certificate throughout the usage period.

activity_main xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    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"
    android:orientation="vertical"
    android:background="#ffaa88"
    tools:context=".MainActivity">

    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_horizontal"
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/word"/>
        <EditText
            android:id="@+id/uriname"
            android:inputType="text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
    </LinearLayout>

    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_horizontal" >
        <Button
            adnroid:id="@+id/post"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="10dp"
            android:text="@string/post" />
        <Button
            android:id="@+id/browser"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="10dp"
            android:text="@string/browser" />
    </LinearLayout>

    <TextView
        android:id="@+id/text_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</LinearLayout>

Android Studioでdependencyを追加する。

file -> project structure を押す

dependency -> libraries dependency を選択する

dependenciesが追加される。

dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    implementation 'com.android.support:appcompat-v7:26.1.0'
    implementation 'com.android.support.constraint:constraint-layout:1.1.0'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
    implementation 'org.jdom:jdom2:2.0.6'
}