ベータコンピューティングの活動や技術、開発のこだわりなどを紹介するブログです。



Jetpack Composeの学習記録

こんにちは、Beta Computing株式会社で学生アルバイトをしていますAndroidアプリケーション開発担当です。
来年4月から正式に入社予定ですので、現在は研修期間としてAndroid開発の勉強を進めています。
今回はAndroid開発のツールキットであるJetpack Composeについて学習した内容を当記事にまとめたいと思います。

Jetpack Composeの公式サイトはこちらになります。 Jetpack Compose UI App Development Toolkit - Android Developers

Jetpack Composeとは?

Jetpack Composeとは、AndroidのネイティブUIを構築するためのツールキットです。
公式サイトでは、従来の方法に比べて簡素化されており、少ないコードでなおかつ高速に動作すると紹介されています。
この章では、従来のXMLベースのレイアウト記法を復習してから、Jetpack Composeの特徴を見ていこうと思います。

従来の方法(XML)

従来のUI構築にはXMLベースのレイアウト記法が採用されていました。 XMLベースのレイアウト記法の基本的な仕組みは、UIの構造をXMLファイルで定義し、それをアプリケーションのコードから読み込んで使用する方式です。
開発者はres/layoutディレクトリ内にXMLファイルを作成し、その中でViewやViewGroupを階層的に記述していきます。
各要素には属性を設定し、IDや幅、高さ、マージンなどの属性を指定します。

これらのXMLファイルは、ActivityやFragmentのKotlinまたはJavaコードからsetContentView()メソッドやinflateメソッドを使って読み込まれます。
読み込まれたレイアウトの個々の要素は、findViewById()などのメソッドを使ってコードから参照され、操作することができます。 この方法では、UIの構造とアプリケーションのロジックが分離されており、それぞれを独立して管理できるのが特徴です。
また、Android Studioのレイアウトエディタを使用すれば、XMLを直接編集せずに視覚的にUIを構築することも可能です。さらに、データバインディングやビューバインディングなどの技術を使用することで、XMLとコードの連携をより効率的に行うこともできます。

簡単な例を以下に示します。

<?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:padding="16dp">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="ログイン"
        android:textSize="24sp"
        android:layout_gravity="center_horizontal"
        android:layout_marginBottom="32dp" />

    <EditText
        android:id="@+id/username_input"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="ユーザー名"
        android:inputType="text"
        android:layout_marginBottom="16dp" />

    <EditText
        android:id="@+id/password_input"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="パスワード"
        android:inputType="textPassword"
        android:layout_marginBottom="24dp" />

    <Button
        android:id="@+id/login_button"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="ログイン" />

</LinearLayout>

この画面は以下のように表示されます。

画面の説明をします。 最上位の要素は垂直方向のLinearLayoutで、画面全体のコンテナとして機能しています。その中に、順に「ログイン」というテキストを表示するTextView、ユーザー名入力用のEditText、パスワード入力用のEditText、そして「ログイン」ボタンとしてのButtonが配置されています。EditTextには入力のヒントが設定され、パスワード用のものは入力内容が隠れるよう指定されています。

Jetpack Compose

XMLベースのレイアウト記法では、画面のデザインとプログラムを別々に書く必要がありましたが、 Jetpack Composeを使うと、すべてをKotlinというプログラミング言語で一緒に書くことができます。
Jetpack Composeでは、ボタンやテキスト、画像などの画面の部品を「関数」として作ります。これらの関数を組み合わせて、アプリの画面全体を作り上げていきます。
また、Composeは画面の変化を自動的に反映してくれます。例えば、ユーザーが何かを入力したり、ボタンを押したりしたときに、関連する部分だけが自動的に更新されます。
これにより、画面の更新について細かく指示を書く必要がなくなり、より簡単にアプリを作ることができます。

では、先ほどの画面をJetpack Composeを使って書き直してみます。

import androidx.compose.foundation.layout.Arrangement
// import文省略

@Preview(showBackground = true)
@Composable
fun LoginScreen() {
    var username by remember { mutableStateOf("") }
    var password by remember { mutableStateOf("") }

    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text(
            text = "ログイン",
            fontSize = 24.sp,
            modifier = Modifier.padding(bottom = 32.dp)
        )

        OutlinedTextField(
            value = username,
            onValueChange = { username = it },
            label = { Text("ユーザー名") },
            modifier = Modifier
                .fillMaxWidth()
                .padding(bottom = 16.dp)
        )

        OutlinedTextField(
            value = password,
            onValueChange = { password = it },
            label = { Text("パスワード") },
            visualTransformation = PasswordVisualTransformation(),
            modifier = Modifier
                .fillMaxWidth()
                .padding(bottom = 24.dp)
        )

        Button(
            onClick = { /* ログイン処理 */ },
            modifier = Modifier.fillMaxWidth()
        ) {
            Text("ログイン")
        }
    }
}

この画面は以下のように表示されます。

先ほどの画面と同じ構成ですが、Jetpack Compose用にもう一度説明します。最上位の要素はColumnコンポーザブルで、画面全体のコンテナとして機能しています。その中に、順に「ログイン」というテキストを表示するText、ユーザー名入力用のOutlinedTextField、パスワード入力用のOutlinedTextField、そして「ログイン」ボタンとしてのButtonが配置されています。OutlinedTextFieldにはラベルが設定され、パスワード用のものは入力内容が隠れるよう指定されています。 このComposeレイアウトは、Kotlinコードとして記述され、Android開発環境で解釈されて実際の画面表示に変換されます。各要素の状態は変数として定義されており、これによってアプリケーションのコードから要素の状態を管理し、操作することが可能になっています。

Jetpack ComposeとXMLの違い

先ほど紹介した二つの記法は主に以下の様な違いがあります。

XML Jetpack Compose
アプローチ 命令型 宣言型
言語 マークアップ言語 Kotlin
UI構築 レイアウトを別のファイルで定義 コードで直接定義
反応性 手動での状態管理 状態に基づく自動更新

Jetpack Composeを採用するメリット

それでは、従来のXMLベースのレイアウト記法に代わって新たにJetpack Composeを導入するメリットを紹介します。 まずXMLベースのレイアウト機能には以下のようなデメリットがあります。

  • コードが冗長になりがち。
    • UIコンポーネントを詳細に定義する必要があるため。
  • 動的UIの管理が複雑。
    • 手動での状態管理やイベントハンドリングが必要であるため。
  • パフォーマンスの問題がある。
    • レイアウトが複雑になるとXMLの解析と描画に時間がかかるようになり、パフォーマンスに影響が出る可能性があります。
  • 編集が難しい。
    • レイアウトが複雑になると、直感的な編集が難しくなる場合があります。

これに対してJetpack Composeには以下のようなメリットがあります。

  • コードが簡潔である。
    • UIコンポーネントをKotlin関数として作成できるため、ボイラープレートコード(変更されることがなく、多くの箇所で書かれているのにも関わらず、プログラミング言語の仕様上省略が不可能なコード)を大幅に削減可能です。
    • 宣言的なアプローチにより、UIの構造がコードの構造と直接対応するため、可読性が向上します。
  • 状態管理が比較的簡単にできる。
    • 状態管理の仕組みが組み込まれているので、専用の関数で状態を管理できます。
  • パフォーマンスの向上が見込める。
    • 効率的なレンダリングエンジンを使用しており、必要な部分のみを再描画することができます。
    • レイアウトの階層が浅くなるため、複雑なUIでもパフォーマンスが向上します。
  • 編集が直感的にできる。
    • リアルタイムプレビュー機能により、コードの変更をすぐに視覚的に確認できます。
    • コンポーネントの再利用が容易になり、複雑なUIでも管理しやすくなります。

以上のような点から、Jetpack Composeを導入することを検討する開発者も増えています。 ただし、XMLベースのレイアウト記法にもメリットはあり、必要に応じて使い分ける必要があります。 例えば古いAndroidバージョンを使用している端末ではJetpack Composeが動作しなかったり、XMLベースの記法の方がライブラリやリソースが豊富だったりします。

Jetpack Composeの導入方法

Android Studioを使用していることを前提条件とします。 最新のAndroid Studioを利用していれば、新しいプロジェクトを作成するだけでJetpack Composeに対応したプロジェクトを作成できるようです。

もし既存のプロジェクトに追加する場合は、アプリのbuild.gradleファイルに次の定義を追加することで対応できます。

android {
    buildFeatures {
        compose true
    }
}

詳しくは公式サイトをご覧ください。
クイック スタート  |  Jetpack Compose  |  Android Developers

Jetpack Composeの使い方

それでは、Jetpack Composeの使い方について紹介します。 本章の内容は以下のサイトから学習した内容を記述しています。
Jetpack Compose を使ってみる  |  Android Developers

コンポーズ可能な関数

UIの記述にはコンポーズ可能な関数を使用します。コンポーズ可能な関数とは以下の特徴を持ちます。

  • @Composableアノテーションを持つ。
  • 他のコンポーズ可能な関数を呼び出せる。
  • 値を返さない。

この関数はUIの内容などを記述するために使用されます 。たとえば以下のような関数です。

@Composable
fun Greeting(name: String) {
    Text(text = "Hello $name!")
}

この関数は画面に「Hello (引数から受け取った名前)!」を表示します。 このような関数を組み合わせて画面を構築していきます。

コンポーネント

コンポーズ可能な関数によって作られる再利用可能なUI部品をコンポーネントと呼びます。コンポーネントには以下のような特徴があります。

  • 独立した機能単位として動作する。
  • 他のコンポーネントと組み合わせて使用できる。
  • データの入力を受け取りUIとして出力する。

コンポーズ可能な関数はコンポーネントを作るための手段であり、コンポーネントはコンポーズ可能な関数によって作られるものです。 つまり、先ほどのGreeting関数もコンポーネントとして機能します。これは単純なコンポーネントでしたが、より複雑なコンポーネントも作成できます。例えば以下のようなものです。

@Composable
fun UserCard(
    name: String,
    age: Int,
    onButtonClick: () -> Unit
) {
    Card(
        modifier = Modifier.padding(32.dp)
    ) {
        Column {
            Text(
                text = name,
                modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp)
            )
            Text(
                text = "Age: $age",
                modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp)
            )
            Button(
                onClick = onButtonClick,
                modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp)
            ) {
                Text("詳細を見る")
            }
        }
    }
}

このコンポーネントは以下のように表示されます。

このように、コンポーネントは様々な粒度で作成することが可能です。

Scaffoldの使い方

最上位のコンポーネントには「Scaffold」と呼ばれるものを使用します。(場合によってはカスタムレイアウトなどを目的に応じて使用することもあります。) Scaffoldを使用することで、Androidアプリの標準的なレイアウトパターンを実現したりすることや、マテリアルデザインを自動的に適用したりすることができます。

それでは試しに使ってみます。 Androidアプリのプロジェクトを作成して、「MainActivity.kt」ファイルに以下のコードを書きます。

// import文は省略

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

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MainScreen() {
    Scaffold(
        topBar = {
            TopAppBar(
                title = { Text("シンプルな画面") }
            )
        }
    ) { paddingValues ->
        Column(
            modifier = Modifier
                .fillMaxSize()
                .padding(paddingValues),
            horizontalAlignment = Alignment.CenterHorizontally,
            verticalArrangement = Arrangement.Center
        ) {
            Text("Hello, Jetpack Compose!")
        }
    }
}

@Preview(showBackground = true)
@Composable
fun MainScreenPreview() {
    MainScreen()
}

以上のコードを実行すると以下の様な画面が表示されます。非常にシンプルな画面です。

では、先ほどのコードのそれぞれの役割について見てみようと思います。 まずは以下の部分です。

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

ここではアプリ起動時に表示される画面を定義しています。今回は次に定義するMainScreenを起動時の画面として設定しています。 では次にMainScreenの定義を見てみましょう。

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MainScreen() {
    Scaffold(
        topBar = {
            TopAppBar(
                title = { Text("シンプルな画面") }
            )
        }
    ) { paddingValues ->
        Column(
            modifier = Modifier
                .fillMaxSize()
                .padding(paddingValues),
            horizontalAlignment = Alignment.CenterHorizontally,
            verticalArrangement = Arrangement.Center
        ) {
            Text("Hello, Jetpack Compose!")
        }
    }
}

ここでScaffoldが登場しています。Scaffoldには以下の様なパラメータを持っています。

  • topBar:ツールバーを指定する。
  • bottomBar:ボトムナビゲーションバーを指定する。
  • floatingActionButton:右下のボタン
  • …など他多数

詳しくは以下から確認することができます。
Jetpack Compose  |  Android Developers

今回はtopBarに「シンプルな画面」と表示するように指定しています。しかしよく見てみると、ScaffoldはtopBarのみを指定して、その後に以下の様に続けています。

Scaffold(
        topBar = {
            // ...内容...
        }
    ) { paddingValues -> // 急にラムダ式のような記述が始まる
        Column(
            modifier = Modifier
            // ...内容...
        )
    }

これはKotlinにおける特有?の書き方で、関数の最後の引数が関数型であれば以下のように書けるようです。

fun function(lambda: () -> Int) : Int { // 関数型を受け取る関数
    return lambda()
}

val result : Int = function { 1 } // 括弧でパラメータを指定
println(result) // 1と表示される

このような書き方を「トレーリングラムダ記法」と呼ぶようです。こちらの仕様については以下のサイトが大変勉強になりました。
Kotlin の trailing lambda は constructor にも使える話 - Qiita

さてここでScaffoldの実装を見てみると…

@Composable
fun Scaffold(
    modifier: Modifier = Modifier,
    topBar: @Composable () -> Unit = {},
    bottomBar: @Composable () -> Unit = {},
    snackbarHost: @Composable () -> Unit = {},
    floatingActionButton: @Composable () -> Unit = {},
    floatingActionButtonPosition: FabPosition = FabPosition.End,
    containerColor: Color = MaterialTheme.colorScheme.background,
    contentColor: Color = contentColorFor(containerColor),
    contentWindowInsets: WindowInsets = ScaffoldDefaults.contentWindowInsets,
    content: @Composable (PaddingValues) -> Unit
) {
// ... 以下実装
}

最後に「content: @Composable (PaddingValues) -> Unit」という引数を受け取っています。
詳しい仕様は省略しますが、この引数に画面のメインコンテンツを定義すれば良いようです。
ただし、必ずpaddingValuesを使用して適切な余白を確保する必要があるようです。

paddingValuesの仕様については以下のサイトが大変勉強になりました。
Jetpack Compose 1.2.0 では Scaffold の content に PaddingValues を必ず設定する - Infinito Nirone 7

Columnの使い方

次に、表示されるコンテンツの中身を見ていきましょう。

Column(
        modifier = Modifier
              .fillMaxSize()
              .padding(paddingValues),
          horizontalAlignment = Alignment.CenterHorizontally,
          verticalArrangement = Arrangement.Center
      ) {
          Text("Hello, Jetpack Compose!")
      }

ColumnはUI要素を縦方向に並べるための基本的なレイアウトコンポーネントです。横方向に並べるにはRowを使用します。
並べる数を増やすには以下の様に要素を追加するだけです。

Column(
        modifier = Modifier
              .fillMaxSize()
              .padding(paddingValues),
          horizontalAlignment = Alignment.CenterHorizontally,
          verticalArrangement = Arrangement.Center
      ) {
          Text("Hello, Jetpack Compose!")
          Text("Hello, Jetpack Compose!")
          Text("Hello, Jetpack Compose!")
      }

また、modifierを使用してサイズや配置を決定することができます。 modifierは多くのコンポーネントに使用できます。例えば先ほどのColumnにはfillMaxSize()が指定されています。
これはColumnが利用可能な画面スペースの最大サイズまで広がるように設定しています。
さらにpadding(paddingValues)によってScaffoldから提供されているpaddingValuesを用いて適切に余白を確保しています。 これによって他のUI要素と干渉することを防いでいます。

modifierには他にもサイズや配置を指定する方法があります。詳しくは以下のサイトが大変勉強になりました。
Jetpack Compose Modifier(修飾) - Qiita
Compose 修飾子  |  Jetpack Compose  |  Android Developers

また、modifier意外にも以下の二つのパラメータを使用してレイアウトを設定しています 。

  • horizontalAlignment = Alignment.CenterHorizontally
    • Column内の要素を横方向の中央に配置するための設定
  • verticalArrangement = Arrangement.Center
    • Column内の要素を縦方向の中脳に配置するための設定

Buttonの使い方

次は、画面にボタンを設置してみます。ボタンも同じくコンポーネントとして配置することができます。 例えば以下のようなコードを書いてみます。

@Composable
fun StyledButton() {
    Button(
        onClick = { /* クリックしたときの処理 */ },
        colors = ButtonDefaults.buttonColors(
            containerColor = Color.DarkGray
        ),
        modifier = Modifier.padding(16.dp)
    ) {
        Text(
            text = "スタイル付きボタン",
            color = Color.White
        )
    }
}

これは以下のように表示されます。

ボタンの背景は、Buttonの引数の「colors」でダークグレーを指定しています。
また、ボタンに表示される文字列はScaffoldと同じようにトレーリングラムダ記法で記述しています。

ボタンを押した時の処理は「onClick」の引数に渡します。 例えば、ボタンを押した際にダイアログを出現させるには以下の様に書きます。

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

@Composable
fun CenteredButton() {
    val context: Context = LocalContext.current;
    Column(
        modifier = Modifier.fillMaxSize(),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Button(
            onClick = {

                Toast.makeText(context, "ボタンが押されました!", Toast.LENGTH_SHORT).show()
            }
        ) {
            Text("ここを押してください")
        }
    }
}

@Preview(showBackground = true)
@Composable
fun CenteredButtonPreview() {
    CenteredButton()
}

このように記述して画面中央のボタンを押すと、下から「ボタンが押されました!」と表示されます。 (これをトーストと呼びます。)

キーボードによる入力

キーボードによる入力もやってみます。まずは以下の様なコードを書きます。

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

@Composable
fun VerySimpleInputField() {
    var text by remember { mutableStateOf("") }

    Column(
        modifier = Modifier.fillMaxSize(),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        TextField(value = text, onValueChange = { text = it }, label = { Text("入力してください") })
    }
    
}

@Preview(showBackground = true)
@Composable
fun VerySimpleInputFieldPreview() {
    VerySimpleInputField()
}

これは以下のように表示されます。

入力欄をタップしてキーボードを入力することで文字を入力することができます。

さて、ここで以下のコードに着目してみます 。

 var text by remember { mutableStateOf("") }

(私にとっては)見慣れない構文が出てきました。まず「by」は委譲を行うための構文です。
変数「text」は別のクラスのメソッドから初期化されています。これを委譲プロパティと呼びます。

詳しくは以下のサイトが大変勉強になりました。
Kotlin Delegate Property について調べてみた - Qiita

次に気になるのは「remember」と「mutableStateOf」です。こちらについては次の章で取り扱います。
ちなみに先ほどのコードがないと(正確にはonValueChange = { text = it }のようにonValueChangeの指定が無いと)キーボードから文字列を入力できません。

状態の更新

状態とは、ユーザーの操作や時間によって変化する値を指します。
先ほどのキーボードによる入力も状態によって管理されています。
状態の更新について説明する前に、コンポーザブルのライフサイクルと再コンポーズについて説明します。

コンポーザブルのライフサイクル

コンポーザブル(@Composableアノテーションが付与された関数のこと)のライフサイクルについて説明します。
コンポーザブルが作成されると、コンポジションと呼ばれるものに配置されます。コンポーザブルはコンポジションによってツリー構造で関係性を管理されています。

詳しくは公式サイトで説明されています。
コンポーザブルのライフサイクル  |  Jetpack Compose  |  Android Developers

このコンポジション内でコンポーザブルが作成された後は、状態と呼ばれるものが更新されることで、0回以上再コンポーズが行われます。
再コンポーズとは、状態の変化によってコンポーザブルを再構築する工程のことです。
そして画面が切り替わったりUI要素が削除された時、対応しているコンポーザブルはコンポジションから退場します。

再コンポーズ

再コンポーズではコンポーザブルが再構築されますが、コンポジション内のコンポーザブル全てが再構築されるわけではありません。

例えば以下のコードを例に考えてみます。

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

@Composable
fun RecomposeDemo() {
    Log.d("Recompose", "RecomposeDemo composing")

    Column(modifier = Modifier.padding(16.dp)) {
        // トップレベルコンポーネント
        TopLevel {
            // 中間レベルコンポーネント
            MiddleLevel {
                // 最下層コンポーネント
                BottomLevel()
            }
        }
    }
}

@Composable
private fun TopLevel(
    content: @Composable () -> Unit
) {
    Log.d("Recompose", "TopLevel composing")

    var topCount by remember { mutableIntStateOf(0) }

    Column {
        Text(
            text = "Top Level (count: $topCount)",
            modifier = Modifier.padding(bottom = 8.dp)
        )

        Button(
            onClick = { topCount++ },
            modifier = Modifier.padding(bottom = 16.dp)
        ) {
            Text("Update Top")
        }

        content()
    }
}

@Composable
private fun MiddleLevel(
    content: @Composable () -> Unit
) {
    Log.d("Recompose", "MiddleLevel composing")

    var middleCount by remember { mutableIntStateOf(0) }

    Column(modifier = Modifier.padding(start = 16.dp)) {
        Text(
            text = "Middle Level (count: $middleCount)",
            modifier = Modifier.padding(bottom = 8.dp)
        )

        Button(
            onClick = { middleCount++ },
            modifier = Modifier.padding(bottom = 16.dp)
        ) {
            Text("Update Middle")
        }

        content()
    }
}

@Composable
private fun BottomLevel() {
    Log.d("Recompose", "BottomLevel composing")

    var bottomCount by remember { mutableIntStateOf(0) }

    Column(modifier = Modifier.padding(start = 32.dp)) {
        Text(
            text = "Bottom Level (count: $bottomCount)",
            modifier = Modifier.padding(bottom = 8.dp)
        )

        Button(
            onClick = { bottomCount++ }
        ) {
            Text("Update Bottom")
        }
    }
}

コンポジション内の構造は以下の通りです。

RecomposeDemo
└── Column
    └── TopLevel
        ├── Text ("Top Level (count: X)")
        ├── Button ("Update Top")
        └── MiddleLevel
            ├── Text ("Middle Level (count: Y)")
            ├── Button ("Update Middle")
            └── BottomLevel
                ├── Text ("Bottom Level (count: Z)")
                └── Button ("Update Bottom")

このコードは大きく分けて三層構造になっています。上から最上層、中間層、最下層があり、それぞれの層にボタンとボタンを押した回数を表示するテキストが設置されています。 また、それぞれの層に以下のようなコードが配置されています。

Log.d("Recompose", "MiddleLevel composing")

これはログを出力するコードです。再コンポーズが行われると必ずこのコードによってログが出力されます。
例えば最上位のコンポーザブルが再コンポーズされると以下のように出力されます。

2024-11-10 10:13:34.375 14056-14056 Recompose  com.example.myapplication  D  TopLevel composing

さらにそれぞれの層にはカウンターを状態として持っています。詳しくは次の章で取り扱います。

var middleCount by remember { mutableStateOf(0) }

この状態はそれぞれのボタンが押されることでインクリメントされます。

 Button(
            onClick = { middleCount++ }, // ここでインクリメント
            modifier = Modifier.padding(bottom = 16.dp)
        ) {
            Text("Update Middle")
        }

ボタンを押すと状態が更新されるので、再コンポーズが起こります。このとき、どの層で再コンポーズが起こってもそれぞれの層でしか再コンポーズが起こりません。
つまりその他の層では再コンポーズが起きず、余計な処理が走りません。

では実際に確かめてみましょう。まずは起動すると以下の様なログが出力されます。

2024-11-10 10:28:10.041 14318-14318 Recompose  com.example.myapplication   D  RecomposeDemo composing
2024-11-10 10:28:10.058 14318-14318 Recompose  com.example.myapplication   D  TopLevel composing
2024-11-10 10:28:10.133 14318-14318 Recompose  com.example.myapplication   D  MiddleLevel composing
2024-11-10 10:28:10.135 14318-14318 Recompose  com.example.myapplication   D  BottomLevel composing

最初は全て順番に作成されるので上の階層からコンポーザブルが構築されます。 ではここで最下層のボタンを押下して再コンポーズを発生させてみます。ログは以下のようになりました。

2024-11-10 10:28:10.135 14318-14318 Recompose  com.example.myapplication   D  BottomLevel composing

中間層や最上層のコンポーザブルでは再コンポーズが起こっていません。これは(自分的には)直感的です。 次に最上層を再コンポーズしてみます。直感的には中間層と最下層も再コンポーズしそうですが…

2024-11-10 10:28:10.058 14318-14318 Recompose  com.example.myapplication   D  TopLevel composing

ログは最上位のものしか表示されません。つまり中間層と最下層では再コンポーズは行われずそのままの状態を維持しています。
中間層で同じことを行っても、最上位層や最下層に影響はありません。

これがJetpack Composeにおける再コンポーズの特徴です。

状態の管理

状態の変化はライフサイクルと関係があることが分かりました。
それを踏まえて状態の管理について見てみます。

内容は以下の公式サイトに沿っています。
状態と Jetpack Compose  |  Android Developers

まず、状態を管理する方法として一番基本的なものは「remember」と「mutableStateOf」を使う方法です。 先ほどの再コンポーズを確かめるコードに以下のような記述があったと思います。

var middleCount by remember { mutableStateOf(0) }

この方法で作成された変数は変更が監視され、変更があるとそのコンポーザブルの再コンポーズが起こります。
先ほどのコードでも、ボタンが押されることでカウンターが更新されて再コンポーズが起こっていました。

@Composable
private fun TopLevel(
    content: @Composable () -> Unit
) {
    Log.d("Recompose", "TopLevel composing")

    var topCount by remember { mutableIntStateOf(0) } // これが更新されると再コンポーズされる

    Column {
        Text(
            text = "Top Level (count: $topCount)",
            modifier = Modifier.padding(bottom = 8.dp)
        )

        Button(
            onClick = { topCount++ },
            modifier = Modifier.padding(bottom = 16.dp)
        ) {
            Text("Update Top")
        }

        content()
    }
}

ただし、この方法では状態が一時的にしか保存されません。例えば画面が回転したり、プロセスが終了すると状態は初期化されてしまいます。
これは設定変更によってアクティビティと呼ばれるものをシステムが破棄するから起こるようです。

詳しくは公式サイトで説明されています。
アクティビティのライフサイクル  |  Android Developers

この状態の破棄を防ぐには「rememberSaveable」を使用します。先ほどのコードの最上層を以下のように書き換えてみます。

@Composable
private fun TopLevel(
    content: @Composable () -> Unit
) {
    Log.d("Recompose", "TopLevel composing")

    var topCount by rememberSaveable { mutableIntStateOf(0) } // ここを書き換える

    Column {
        Text(
            text = "Top Level (count: $topCount)",
            modifier = Modifier.padding(bottom = 8.dp)
        )

        Button(
            onClick = { topCount++ },
            modifier = Modifier.padding(bottom = 16.dp)
        ) {
            Text("Update Top")
        }

        content()
    }
}

// 以下は同じ

まずは起動して、それぞれのボタンを5回ずつ押下してみます。

この状態で画面を横にします。すると…

最上層のカウントは保持されますが、それ以外の層のカウントは0に戻ってしまいました。
以上で状態が保持されることが確認できたかと思います。

ここまで見ると、rememberよりもrememberSaveableを使った方が良さそうですが、場合によって使い分ける必要があります。
例えば、rememberSaveableの方がメモリ消費量が比較的多くなるため、 乱用すると不必要にメモリを圧迫してパフォーマンスが落ちることがあります。

状態ホイスティング

状態ホイスティングとは、状態を上位のコンポーネントに持ち上げるデザインパターンです。
この方法を用いることで、より柔軟で保守性の高いコンポーネントを作成できます。

例えば以下のコードを見てください。

// ステートフルな実装(ホイスティング前)
@Composable
fun NameInput() {
    var name by remember { mutableStateOf("") }
    TextField(
        value = name,
        onValueChange = { name = it }
    )
}

これを状態ホイスティングを使用して書き直したものが以下になります。

// ステートレスな実装(ホイスティング後)
@Composable
fun NameInput(
    name: String,
    onNameChange: (String) -> Unit
) {
    TextField(
        value = name,
        onValueChange = onNameChange
    )
}

// 状態を管理する親コンポーネント
@Composable
fun NameScreen() {
    var name by remember { mutableStateOf("") }
    NameInput(
        name = name,
        onNameChange = { name = it }
    )
}

このように実装するとどのようなメリットがあるのでしょうか。

まず、再利用性が向上します。
先ほどのコードで言えば、NameInputは複数の入力フィールドに使い回すことができます。

@Composable
fun UserProfileForm() {
    var firstName by remember { mutableStateOf("") }
    var lastName by remember { mutableStateOf("") }
    var nickname by remember { mutableStateOf("") }
    
    Column(modifier = Modifier.padding(16.dp)) {
        NameInput(
            name = firstName,
            onNameChange = { firstName = it }
        )
        
        NameInput(
            name = lastName,
            onNameChange = { lastName = it }
        )
        
        NameInput(
            name = nickname,
            onNameChange = { nickname = it }
        )
        
        // 入力値を使った処理が可能
        Text("Full name: $firstName $lastName ($nickname)")
    }
}

バリデーションを追加することも容易です。

@Composable
fun ValidatedNameForm() {
    var name by remember { mutableStateOf("") }
    var isError by remember { mutableStateOf(false) }
    
    Column {
        NameInput(
            name = name,
            onNameChange = { 
                name = it
                isError = it.length < 3
            }
        )
        
        if (isError) {
            Text(
                text = "名前は3文字以上必要です",
                color = MaterialTheme.colorScheme.error,
                modifier = Modifier.padding(start = 16.dp)
            )
        }
    }
}

他のフィールドと連携することもできます。

@Composable
fun LinkedNameFields() {
    var firstName by remember { mutableStateOf("") }
    var lastName by remember { mutableStateOf("") }
    var fullName by remember { mutableStateOf("") }
    
    Column {
        NameInput(
            name = firstName,
            onNameChange = { 
                firstName = it
                fullName = "$it $lastName"
            }
        )
        
        NameInput(
            name = lastName,
            onNameChange = { 
                lastName = it
                fullName = "$firstName $it"
            }
        )
        
        // 読み取り専用の結合フィールド
        NameInput(
            name = fullName,
            onNameChange = { }  // 変更不可
        )
    }
}

これらは、NameInputが内部に状態を持たないステートレスな実装になっているからです。 他にもテストが容易になるといった利点もあります。

詳しくは公式サイトをご覧ください。
状態と Jetpack Compose  |  Android Developers

最後に

今回はJetpack Composeの基本について学びました。宣言型の記述はFlutterを少し触っていたので無理なく学習を進めることができました。 また、再コンポーズの仕組みは複雑ではありますが、パフォーマンスが考慮されていている良い仕組みだと感じました。 今回学んだ内容を活かして、次回はJetpack Composeを用いて実際にアプリケーションを構築してみようと思います。

競技かるたONLINEの裏話(その2)

こんにちは、Beta Computing株式会社の村松です。
前記事に引き続き「競技かるた ONLINE」の裏話をお話いたします。

開発秘話

漫画「ちはやふる」をきっかけに競技かるたに興味を持った私たちですが、競技かるたのルールは漫画を通して把握しているつもりでした。また、アプリ開発にあたり競技かるたのルールをインターネットで検索していました。
そして、自分たちなりに試行錯誤し、「競技かるた ONLINE」の原型となるアプリを開発しました。

その後、たまたま会社の近くにかるた教室があることを知ったので、開発したアプリを持ってお邪魔させていただきました。 皆様に感動してもらえるのではないかと期待して、アプリをお見せしましたが、以下のようなご指摘を受けました。

  • 札のフォントが公式札と違う
  • から札の扱い方が違う
  • 札押しの考え方が違う
  • 決まり字の考え方が違う
  • CPUの取るタイミングがおかしい
  • CPUの定位置がおかしい
  • CPUの送り札がおかしい
  • 暗記時間やから札が出た際の時間の使い方が気におかしい
  • 画面サイズ上25枚:25枚は並べられないのは仕方ないとして、友札を多くしてほしい

などなど

特にかるた教室に子どもたちの反応を見ると、ゲームアプリということで楽しそうに遊んではくれたものの、競技かるたという内容に対しては「ぜんぜんちがーう!」「えー!意味わからない!」という声が聞こえてきました。

更に、かるた教室の先生からは「あなた達は実際にかるたをやったことがないのではないか。まずは百首を覚えて、練習に参加し、大会に出なさい。」とごもっともなご意見を頂きました。 そこから、私達は1年以上かるた教室に通い、百首は当然暗記し、大会に出て少し勝ち進めるまでになりました。

漫画やインターネットで競技かるたのルールを理解していたつもりでしたが、実際に真剣にかるたに取り組んでみて、やっと当初の指摘の意味が分かってきました。

  • 札は大石天狗堂様の札でないと取りづらい
  • から札はルールとして必須、決まり字変化にも影響があり、暗記し直す時間にもなる
  • 取り判定の厳密なタイミング
  • CPUは決まり字が読まれてから取るべきだが、決まり字変化や残り枚数によってもスピードが変化するべき
  • 定位置や送り札にも戦略がある
  • アプリでは基本8枚:8枚の試合だが、決まり字変化を楽しむために、使われる札はランダムではなく、計算が必要

などなど

これらの経験や知識を反映させたものが、現在の「競技かるた ONLINE」でございます。 囲い手やフライング判定など、アプリ上ではどうしても再現させることができなかったルールもございますが、できる限り競技かるたのルールを再現するよう努力しております。

このように、インターネットでルールを理解したつもりで開発しましたが、実際のかるた競技者から見ると本質を理解できていないアプリだったということがわかりました。
全日本かるた協会様、各地域のかるた教室様、各大会で出会った選手様、大石天狗堂様、読手様等、皆様のご協力の上で開発することができたアプリです。改めてお礼申し上げます。本当にありがとうございます。

なお、このエピソードは「競技かるた ONLINE」だけではなく、ソフトウェア開発やスマートフォンアプリ開発全般に通ずるものがあると考えています。
以降弊社では、スマートフォンアプリを開発する際は、アプリを利用される業種の勉強や、現場の調査を十分に行い、設計・開発するよう心がけています。どのアプリも関係者の皆様のお力を借りながら、開発しております。

開発費

前記事でもお話しましたが、弊社のメイン事業はソフトウェア開発の受託業務でございます。 受託業務の合間に自社製品開発として「競技かるた ONLINE」の開発を進めておりました。

「競技かるた ONLINE」は開発着手から初回リリースまで約1年半かかっております。 その間にかるた教室に通ったり、受託業務の合間にアプリを何度も修正したりということもございますが、単純な期間だけでいうと約1年半になります。

「競技かるた ONLINE」の初回リリースまでにかかった費用は主に以下の通りです。

  • 人件費
  • 読手様の音声収録費
  • イラストデータの素材費
  • BGM・SEデータの素材費
  • サーバ費
  • 調査や関係者とお会いするための旅費交通費
  • 広告宣伝費

などなど

本来であれば多額の費用がかかりますが、各種補助金(小規模事業者持続化補助金、新製品開発補助金)を利用することで、約300万円の実費負担で開発することができました。
その他、サービス継続のために運営費としてサーバ費や保守対応費用がかかっております。

「競技かるた ONLINE」は会社の広告宣伝として、無料でかつ広告表示もできる限り少なくリリースしております。 ぜひとも弊社スマートフォンアプリ開発のBeta Computing株式会社を認識いただけると大変嬉しく思います。

終わりに

「競技かるた ONLINE」の開発や運営の裏話を中心にお話しましたが、私達も競技かるたを習って大会に出場することで、より一層競技かるたの奥深さと楽しさを知ることができました。同時に調査を進めることで、競技かるた界が抱える課題なども多く知ることができました。
弊社では「ちはやふる基金」「全日本かるた協会」「各大会への協賛」「地元かるた教室への貢献」等、少額ではありますが支援しております。 「競技かるた ONLINE」も競技かるた界の発展に少しでも役立つことを願って、開発と保守を続けてまいります。

もし弊社にご協力できそうなことがあればお気軽にお問い合わせください。 どうぞよろしくお願いいたします。

競技かるた ONLINEについてのお詫びと裏話(その1)

こんにちは、Beta Computing株式会社の村松です。 今日は「競技かるた ONLINE」についてお詫びと裏話をお話いたします。

Android版の「競技かるた ONLINE」がインストールできない件について

まず、現在(2024年6月7日時点) Android版「競技かるた ONLINE」がGoogle Play ストアからインストールできない状況となっております。大変申し訳ございません。 本件、ユーザー様からのお問い合わせも多数頂いており、期待してくれている方も多くいる中で、ご迷惑おかけしている状況です。

こちら、原因は判明しておりまして、現在対応中でございます。 2024年9月頃にアップデート完了する予定ですので、もうしばらくお待ちいただけますようお願いいたします。 同時に、機能追加や読手音声の追加も予定しておりますので、ご期待ください。

なお、「競技かるた ONLINE」の更新状況については、公式X(旧Twitter)の方でもお知らせしてまいります。 よろしければフォローいただければと思います。 https://x.com/karutaapp

Beta Computing株式会社について

本記事をご覧の方の中には、弊社についてご存じない方も多いと思いますので、まずは会社紹介をさせていただきます。

弊社は2015年に創業しました石川県の津幡町(金沢市の隣)にあるスマートフォンアプリ開発に特化したソフトウェア開発会社です。 「競技かるた ONLINE」や「かるた読唱 〜百人一首読み上げ〜」を自社製品としてリリースしておりますが、かるた専門の会社ではなく、ゲーム専門の会社でもございません。
(かるたもゲームも大好きです。今後もかるたアプリやゲームアプリも開発してまいります。)

弊社のメイン事業は、ソフトウェア開発の受託業務でございます。 もし興味を持っていただけましたら、弊社HPの会社概要開発実績をご覧いただければと思います。

「競技かるた ONLINE」の開発経緯について

「競技かるた ONLINE」は2019年4月25日にリリースしました。

karuta.betacomputing.co.jp

「競技かるた ONLINE」を開発したきっかけ

「競技かるた ONLINE」を開発したきっかけは大きく分けて2つございます。

1.Beta Computingの自社製品がなかった
弊社のメイン事業はソフトウェア開発の受託業務ですが、創業当時は営業活動に苦戦しておりました。

というのも、創業当時は会社としての開発実績がなかったために、お客様に弊社のアプリのイメージや弊社の技術力を伝えることが難しいという課題がございました。

また、ソフトウェア業界には「秘密保持契約」というものがございまして、弊社で開発したアプリでもお客様企業の名前で世の中にリリースされることが多く、弊社の名前は外に出ない上、弊社の開発実績として掲載・紹介できないものがいくつも存在します。
(現在は弊社HPに「開発実績」のページがございますが、それぞれお客様の許可を頂けたものを掲載させていただいております。お客様には感謝申し上げます。)

そこで、自社製品を開発することで、営業時に弊社の技術力を伝えやすくしようと考えました。 悩んだ結果、オンライン対戦のゲームアプリであれば、技術的な難易度が高いため、営業にも役立つという結論になりました。 また、ゲームアプリであれば一般ユーザーに楽しんでもらえる上、広告塔として弊社の知名度向上にも繋がるのではないかと考えました。

2.漫画「ちはやふる」がおもしろく、影響を受けた
ゲームアプリを開発しようと考えましたが、具体的にどのようなアプリにするかは決めておりませんでした。

ちょうどその頃、弊社メンバーが「ちはやふる」にハマっていたこともあり、社内でかるた大会をしようとしました。しかし、実際に札や読み上げ音声を用意するのは面倒なので、簡単にアプリで遊べるものがないか調べたところ、競技かるたのルールに沿ったゲームアプリがないことに気が付きました。

そこで、競技かるたのアプリを開発しようということになりました。「ちはやふる」のおかげもあり、楽しんで開発に取り組むことができました。

企画時の余談

余談ですが、「競技かるた ONLINE」企画当初は、ゲーム性を持たせ、相手の札を並び替えてしまうような自分に有利なアイテムや、相手を1回休みにするような相手が不利になるアイテムなどを設ける案もありました。競技かるたのルールを忠実に再現すると、百人一首をすべて覚えている前提になるので、ターゲットとなるユーザー数が少なすぎると考えたからです。そのため百人一首を知らないユーザーでも遊べる仕組みにしようという案がありました。 しかし、競技かるたのルールに沿ったゲームアプリがないというきっかけから開発を決めたアプリだったので、実際に競技かるたをやっているユーザーや、これから競技かるたを本格的に始めるユーザーが楽しめるアプリを開発することにしました。

開発面でのメリット

また、競技かるたを題材にしたゲームアプリは開発面でも多くメリットがありました。その中でも3つほど紹介します。

1.必要なイラスト素材が少ない
ゲームアプリを開発するとなると、例えばアクションゲームやRPGゲームであれば、ステージの数だけステージ検討やイラストデータの素材、キャラクターの素材などが必要になり、莫大な費用がかかってしまいます。開発当時、弊社は3人体制の小さな会社だったので、開発費と時間を極力抑える必要がありました。 かるたであれば、最低限100枚の札と畳の画像データ、読手音声のデータさえあれば開発できます。

2.札を飛ばす物理演算
競技かるたでは、漫画や映画で札を弾き飛ばしている様子が描かれますが、札押しというルールがあります。ただ札をタッチするだけではなく、ルールに沿って札を弾き飛ばしたいと考えました。また、ユーザーにとっても札を弾き飛ばせることが楽しいと考えたからです。 この札押しを再現するためには、物理演算という札の動きを計算して表現する必要があります。札押しの再現が技術力のアピールに繋がると考え開発しました。

その他にも、和の雰囲気を伝える見た目、説明書がなくても使いやすいUI/UXなどにこだわって開発しました。特に伝統的な日本語の文字遊びを意識しており、テキスト表示はメニュー画面を含め、縦書きになるようこだわっています。実は文字の縦書きをスマートフォンアプリ上で綺麗に見せることは大変難しく、他のアプリではあまり見られない高度な技術力アピールになっています。

3.リアルタイムオンライン対戦
スマートフォンアプリでは通信処理が絡むことが多いです。技術力宣伝のためにもネットワーク周りの処理を実装したかったのですが、かるたゲームであればリアルタイムにオンライン通信する必要があります。 リアルタイムオンライン対戦を実現することで、業務アプリの通信処理でも開発できる技術力があるというアピールに繋がります。

よくある質問

こちらも余談ですが、オンライン対戦についてはユーザー様からのお問い合わせも多いので、よくあるご質問2点にお答えします。

よくある質問1:オンライン対戦で自分の方が早く取ったにも関わらず、相手の取りになった
判定の取得秒数につきまして、決まり字が読まれてから取るまでの時間を端末内で0.001秒単位で計測し、何秒で取ったかを解析しております。オンライン対戦では各自計測した秒数を相手と突き合わせて、早い方の取りになります。ですので、取りの判定に誤りはございません。 しかし、自分もしくは相手の端末スペックが低い場合やネットワーク環境が悪い場合は、試合中に表示される取りのエフェクトが遅れて表示されることがございます。 特にネットワーク環境が悪い場合は、取ったことを対戦相手に伝えるまでの通信に時間がかかってしまうのでアプリ側ではどうしても改善できません。 そのため、エフェクトは遅れて表示されるが、実際は相手がもっと早く取っていたという状況が発生し、自分の方が早く取ったにも関わらず、相手の取りになったという見え方になってしまいます。 恐れ入りますが、取りの判定結果が正しいもので、エフェクトは参考程度に見えるものとご了承いただきますようお願いいたします。

よくある質問2:札押しをしたのに取り判定にならない
札の払いに関しましては、競技かるたの公式ルールに従いまして、札が競技線の外に完全に出た瞬間に取りの判定になります。札の一部でも競技線の内側に残っていると取り判定にはなりません。 ですので、札が競技線の外に完全出る前に、対戦相手に札直で触れられると相手の取りになります。

どちらもコンマ何秒の世界になりますので、技術的に実現が難しく、表現も難しいため、分かりづらい部分もあるかと思いますが、ご了承いただけると幸いです。

開発秘話 や 開発費と運営費 について

記事が長くなりましたので、次の記事に分けて書きたいと思います。

続きます。

大学や高専で会社紹介をさせていただきました

こんにちは、Beta Computing株式会社の村松です。

ブログの更新頻度が減っておりました。 閲覧してくれている方がいると信じて、今後更新回数を増やしていこうと思います。 よろしくお願いいたします。

講義活動について

2017年頃から教育機関での講義活動を依頼される機会が増え、大学、専門学校、高校で講師として登壇させていただくことが度々ございました。 ありがたいことに、以降毎年講師を務める機会をいただいております。

今年も、金沢大学、金沢工業大学、石川工業高等専門学校で講義を行いましたので、その内容をご紹介いたします。

金沢大学大学院「イノベーション方法論」

金沢大学大学院の「イノベーション方法論」では、私の起業の経緯と自己分析の重要性についてお話ししました。 対面授業でございましたが、講義室に入り切らない人数だったため同時にオンラインでの配信も行う形式でした。 真剣にお話を聞いていただき、鋭い質問も多く頂きました。

金沢大学融合学域スマート創成科学類「アントレプレナー演習Ⅰ」

金沢大学融合学域スマート創成科学類の「アントレプレナー演習Ⅰ」では、ISAと金沢大学の連携活動の一環として、弊社の紹介をさせていただきました。 スマート創成科学類は、文系理系の概念に囚われず、仮想と現実の融合を実装し,イノベーションの創成をリードする人材の養成を目的としております。 学生の皆様にはぜひ、弊社はもちろんのこと、石川県内の企業に興味を持っていただければと思います。 時間いっぱいまで多くの質問を頂き、講義後も先生方や学生の皆さまとお話する機会をいただきました。

金沢工業大学情報フロンティア学部メディア情報学科「進路セミナーⅠ」

金沢工業大学情報フロンティア学部メディア情報学科の「進路セミナーⅠ」では、地域での仕事や就職活動についてもお話ししました。 私も金沢工業大学出身ですので、金沢工業大学卒業から今に至るまでの話や、 地域で働くことの意味、就職活動において重要な要素である自己PRや志望動機の作成方法、面接の対策などについても説明しました。 レポート課題にて質問を多く頂きましたので、フィードバックをお送りしました。

石川工業高等専門学校電子情報工学科「実験3」

石川工業高等専門学校電子情報工学科の「実験3」では、お時間を頂いて弊社の紹介を行いました。 石川高専卒業生の弊社社員からも、卒業生としてアドバイスのメッセージを伝えました。

また、逆に高専生の皆様の活動をご紹介いただきました。興味深い活動ばかりで技術の高さと行動力に大変感銘を受けました。 授業後の放課後も、研究室の学生の皆様とお話する場を設けていただき、交流を深めることができました。

講義の振り返り

これらの講義を通じて出会った学生たちは、本当に素晴らしく、全員が非常に優秀でした。 私たちも負けていられないなと大きな刺激をもらいました。

また、学生の皆様に就職先の候補をお聞きしたところ、大手企業だけでなく、中小企業やベンチャー企業に興味がある学生も多くいることに驚きました。 自分で起業したいという学生もおり、将来について真剣に考えている様子が印象に残っています。

今回は講師という立場でお話させてもらいましたが、いつか皆様とビジネスパートナーとしてお話できれば幸いです。 ぜひBeta Computingという会社があることを覚えておいてもらえると嬉しいです。

採用活動のご案内

弊社では現在、採用活動を積極的に行っており、才能ある若い世代を迎え入れたいと考えています。 もし、弊社に興味を持っていただける方がいらっしゃいましたら、ぜひご応募いただきたいと思います。

弊社は、社員一人ひとりが持つスキルと個性を最大限に活かし、共に成長できる環境を提供しています。 特に、新しい技術やイノベーションに興味を持ち、自らの手で未来を切り拓いていきたいという意欲を持つ方を歓迎しています。

採用情報はこちら

また、弊社ではインターンシップも募集しております。 インターンシップは、学生たちが実際の業務を体験し、自分のキャリアを具体的に考える貴重な機会です。 実務経験を積むことで、より深い理解とスキルの向上が期待できます。 インターンシップに参加することで、学生たちは企業の文化や働き方を肌で感じ、自分の適性や興味を確認することができます。 弊社のインターンシップに興味がある方は、ぜひお問い合わせください。

インターンシップ情報はこちら

最後に

講義活動を通じて、多くの優秀な学生たちと出会えたことは私たちにとっても得るものが多くあります。 皆様の熱意には感銘を受けています。これからも教育機関との連携を深めていければと考えております。 引き続き、教育機関との共同研究やプロジェクトにも積極的に参加してまいりますので、お力添えのほどお願いいたします。

これからも皆様のご支援とご協力を賜りますよう、お願い申し上げます。どうぞよろしくお願いいたします。

Androidアプリケーション開発を経験して学んだこと#フレームワーク&ライブラリ編

この記事について

こんにちは、Beta Computing株式会社でアルバイトをしていますAndroidアプリケーション開発担当です。
前回の「Androidアプリケーション開発を経験して学んだこと#ツール編」に引き続き、学んだことを書いていきます。今回はフレームワークやライブラリについてです。

学んだこと

Gitにおけるブランチ名

私はアプリケーション開発に関わる以前、Gitに関する知識が浅く、ブランチを分けることすらしていませんでした。
しかし開発となるとそうはいかず、ブランチを適切に作成し進める必要があります。
そこで問題になったのがブランチの命名方法です。複数人で開発を行う際はブランチの命名は適当では無く、社内で共通ルールを決めて命名する場合があります。
例えば以下の様な命名方法があります。

  • git-flowモデル
    • masterブランチ - メインブランチ
    • developブランチ - 次回リリースする際にmasterマージするブランチ
    • featureブランチ - 新しい機能を開発する際のブランチ

その他については以下のサイトが勉強になりました。

参照:Gitのブランチモデル(git-flow, GitHub Flow, GitLab Flow)のブランチ名まとめ


ドキュメンテーションコメント

過去の私はとにかくいろいろな場所にドキュメンテーションコメントと呼ばれるものを書いていましたが、その役割についてはあまり理解していませんでした。今回、先輩社員の方からアドバイスをいただいたので、それについてまとめたいと思います。

まず、ドキュメンテーションコメントとはクラスや変数・メソッドなどにそれらの概要や目的を説明するために書くコメントのことです。 Javaなどではドキュメンテーションコメントを書いておくことによって、クラスの仕様書のようなもの(Javadocなど)を簡単に作成できたりします。
例えば以下の様に書くことが出来ます。

/**
 * クラスの説明
 **/
class TestClass {
  /**
   * メンバの説明
   **/
  int value = 0;
}

ドキュメンテーションコメントはとても便利なのですが、常にどこでも使えばよいというわけではないようです。
privateメソッドなどにはドキュメンテーションコメントではなく、普通のコメントを使用します。

/**
 * クラスの説明
 **/
class TestClass {
  // privateメソッドの実装内容(通常コメントを使用する)
  private void function(int value) {
    ...
  }
}

ドキュメンテーションコメントについては以下のサイトが勉強になりました。

参照:Javadoc ドキュメンテーションコメントの書き方


ktlint(コードスタイルのチェック)

ktlintとはKotlinのコードスタイルをチャックしてくれるライブラリです。
今回はそのktlintをGradleプラグインとして導入できる「org.jlleitschuh.gradle.ktlint」を使用しました。
このライブラリを導入した後に、コマンドラインで「./gradlew ktlintCheck」を実行するだけで、プロジェクト内のコードをチェックしてくれます。下の画面をご覧ください。

画像のように「BUILD SUCCESSFUL」と表示されればコードスタイルに問題が無かったということになります。
逆にビルドに失敗すると、コードスタイルが間違っているところを提示してくれます。
ビルドが成功するまでコードを修正することでコードを綺麗に保てます。

ktlintについては以下のサイトが勉強になりました。

参考:【Android】コードスタイルをチェックする ktlint の導入方法


Timber(ログの出力方法)

TimberとはAndroidアプリケーションにおけるログ出力を便利にするライブラリです。
まず、普通にAndroid studioでログを出力させるには、以下のようにします。

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

        Log.d("MainActivity","ログ出力テスト")
    }
}

すると以下の様にログが出力されます。

ここで先ほどのコードをみていただきたいのですが、Log.d() 内には二つの引数を渡す必要があります。
左側にはタグを、右側にはメッセージを渡しています。そして出力では左側の方にタグが、右側の方にメッセージが出力されています。

それに対して、Timberを使用すると以下の様に書くことが出来ます。

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

        Timber.d("ログ出力テスト")
    }
}

Timberを使うと、ログ出力時にフォーマット書式を組み立ててくれたり、ビルドバリアントごとにログ出力の設定 (Tree) をカスタマイズできたり、ログ出力の書式に関してLintによる警告を追加してくれたりします。
特に、デバッグ用途でログを使用する際は、ライブラリ標準のDebugTreeというTree (ログの出力制御ロジック) を使用するのが一般的ですが、このDebugTreeを使用している場合、わざわざ自分でログのタグ名を指定せずとも、呼び出し元のクラス名を元に勝手にタグ名を設定してくれたりします。
ただし、このDebugTreeはパフォーマンスの関係上リリースビルドでは使用しないことが多く、注意が必要です。

また、Timberを導入すると、元々のLogを使ってログを出力していた箇所を指摘してくれます。以下の画像をご覧ください。

ここからTimberへの書き換えはワンクリックで可能です。

大変便利なTimberですが、導入は以下の手順で行います。 まず、build.gradleファイルに次の記載が必要です。

dependencies {

    implementation("com.jakewharton.timber:timber:5.0.1")
}

追記した後は右上の「Snyc Now」を押して同期します。
同期が完了した次は、Applicationクラスの設定します。
以下の様なクラスを追加で作成します。「MyApplication」は適宜命名してください。重要なのはApplicationクラスを継承していることです。

import android.app.Application
import timber.log.Timber

class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()

        if (BuildConfig.DEBUG) Timber.plant(Timber.DebugTree())
    }
}

こちらでTreeにDebugTreeを設定しています。
ここで自作したTreeを設定することも可能です。
Treeを自作することで、出力時のルールの変更、ログの外部出力といったカスタマイズを追加することが出来ます。
※Applicationクラスについては以下のサイトが簡潔に説明されていました。

参照:Applicationクラスとは何か?

Applicationクラスを継承したクラスを作成した後は、プロジェクトの「AndroidManifest.xml」ファイルに以下の記述を追加します。

    <application
        android:name = ".MyApplication"

以上がTimberの使い方および導入方法です。 Timberについては以下のサイトが勉強になりました。

参照:AndroidのLogとTimberについて


Hilt(DIコンテナ)

Hiltは依存性(オブジェクト)の注入を簡素化することが出来るライブラリです。
依存性の注入とは、あるオブジェクトや関数が、依存する他のオブジェクトや関数を受け取るデザインパターンです。
これにより、プログラムの柔軟性が向上し、テストが容易になります。 導入方法や使い方は説明するとそれだけで記事が書けてしまいそうなので、今回は割愛します。

Hiltについては以下のサイトが勉強になりました。

参照:Hiltを使ってみよう


Ktor(Http通信)と Kotlinx.serialization.json

Ktorとは、Kotlinで動くWebアプリケーションフレームワークです。
KtorのHttpクライアントを使うことで、Http通信を簡単に行うことができます。 さらに、後述する「Kotlinx.serialization.json」と組み合わせることで、パースも簡単かつ高速に行うことが出来ます。

Kotlinx.serialization.jsonはJsonのパースを簡単かつ高速に行うことが出来るライブラリです。
Httpクライアントと一緒に使うことで、Http通信を簡単かつ高速に行えます。

KtorのHttpクライアントとKotlinx.serialization.jsonを使うには、モジュールのbuild.gradleファイルに次の記載が必要です。

plugins {
    // Kotlin Serialization関連
    id 'org.jetbrains.kotlin.plugin.serialization'
}

dependencies {

    // Ktor(ケイター)関連
    implementation "io.ktor:ktor-client-core:2.3.2"
    implementation "io.ktor:ktor-client-cio:2.3.2"
    implementation "io.ktor:ktor-client-json:2.3.2"
    implementation "io.ktor:ktor-client-serialization:2.3.2"
    implementation "io.ktor:ktor-client-content-negotiation:2.3.2"
    implementation "io.ktor:ktor-serialization-kotlinx-json:2.3.2"

    // Kotlin Serialization関連
    implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1"
}

また、プロジェクトのbuild.gradleファイルにも以下の記載が必要です。

plugins {
    id 'org.jetbrains.kotlin.plugin.serialization' version '1.6.20' apply false
}

Kotlinx.serialization.jsonにいては、詳しい設定や使い方は以下のサイトが大変わかりやすく説明しています。

参照:【Kotlin】Kotlin Serialization で JSON をパースする

設定は以上です。
次に使い方ですが、APIコールしてデータを受け取りログに出力するアプリケーションを例に考えてみます。 まずAPIコールには以下のユーザー情報をランダムに作成するAPIを使用します。

参照:RANDOM USER GENERATOR

このAPIを使うと以下の様なレスポンスが返ってきます。

{
    "results": [
        {
            "gender": "female",
            "name": { // 名前
                "title": "Ms",
                "first": "Rena",
                "last": "Großmann"
            },
            "location": { // 住所
                "street": {
                    "number": 7366,
                    "name": "Parkstraße"
                },
                "city": "Waghäusel",
                "state": "Bayern",
                "country": "Germany",
                "postcode": 11878,
                "coordinates": {
                    "latitude": "-54.8818",
                    "longitude": "149.4619"
                },
                "timezone": {
                    "offset": "-3:00",
                    "description": "Brazil, Buenos Aires, Georgetown"
                }
            },
            "email": "rena.grossmann@example.com",

// 以下情報が続く...

このレスポンスをパースしてログに出力してみます。 まずはこのJsonをパースするためのデータクラスを作成します。
「Kotlinx.serialization.json」を使ったパースでは、受け取るJsonの構造と同じ構造をしたデータクラスを作るだけでその型としてJsonデータをパースして受け取ってくれます。
さらに、Jsonの必要な一部のデータのみを受け取ることも可能です。

では、先ほどのレスポンスから名前と住所のみを受け取るデータクラスを定義してみましょう。 以下が受け取る対象です。

{
    "results": [
        {
            //...
            "name": {
                "title": "Miss",
                "first": "Naja",
                "last": "Andersen"
            },
            "location": {
                //...
                "city": "Stenderup",
                "state": "Syddanmark",
                "country": "Denmark",
                //...
            },
            //...
        }
    ],
    //...
}

以上の内容をデータクラス化します。まずは以下のコードをご覧ください。

package com.example.myapplication

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class UserJson (
    @SerialName("results") val results : List<Result>?
)

@Serializable
data class Result (
    @SerialName("name") val name: Name?,
    @SerialName("location") val location: Location?
)

@Serializable
data class Name (
    @SerialName("title") val title: String?,
    @SerialName("first") val first: String?,
    @SerialName("last") val last: String?
)

@Serializable
data class Location(
    @SerialName("city") val city: String?,
    @SerialName("state") val state: String?,
    @SerialName("country") val country: String?
)

Jsonの階層構造をそのままデータクラスで表現しています。
Jsonとデータクラスを紐付けるには@Serializable アノテーションをそれぞれのクラスに付与します。
@SerialName アノテーションは、変数名とJsonにおけるキーの値が異なる場合に必要になります。

次に以下の様なクラスを用意します。
※サンプルコードのため細かなエラーハンドリングは割愛しています。

package com.example.myapplication

import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.engine.cio.CIO
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.client.request.get
import io.ktor.client.statement.HttpResponse
import io.ktor.serialization.kotlinx.json.json
import kotlinx.serialization.json.Json
import timber.log.Timber

class UserQueryService {
    // HttpClientおよびJsonパーサーを使い回します。
    private val _client: HttpClient by lazy {
        HttpClient(CIO) {
            install(ContentNegotiation) {
                json(
                    Json {
                        ignoreUnknownKeys = true
                        coerceInputValues = true
                    }
                )
            }
        }
    }

    suspend fun getRandomUser() {
        val request = "https://randomuser.me/api/"

        val result: UserJson
        try {
            val response: HttpResponse = _client.get(request)
            //  TODO: 今回は割愛しますが、エラーハンドリングを行います。

            result = response.body()
        } catch (ex: IOException) {
            Timber.e("通信エラー")
            //  TODO: 今回は割愛しますが、エラーハンドリングを行います。
        }
        Timber.d("パース成功")
        Timber.d(result.toString())
    }
}

このクラスにはメンバーに変数の_client と、メソッドのgetTandomUser() があります。

_client はHttpクライアントとJsonパーサーの役割を持っています。
この変数は通信処理が起こるたびにインスタンスを作り直すのでは無く、メンバーとして宣言し使い回す方が効率的です。

getTandomUser()_client を用いて実際にHttp通信とJsonのパースを実行します。
このように記述することで、APIコールの結果のレスポンスボディがUserJson型にパースされ、APIの処理結果を取り扱うことが出来ます。 この関数をコルーチンを用いて実行すると以下の結果が得られました。

  UserJson(result=[Result(name=Name(title=Ms, first=Namratha, last=Uchil), location=Location(city=Thoothukudi, state=Tripura, country=India))])

ちゃんとパース出来ているようです。

以上がHttpクライアントとKotlinx.serialization.jsonを使ったHttp通信とJsonパースです。
このコードを動かすにはコルーチンを使った非同期処理の実装が必要ですが、コルーチンについては次の設計編で紹介します。

まとめ

今回はAndroidアプリケーションの開発を通して、学んだことのうちフレームワークとライブラリに関するものを集めました。フレームワークなどを扱うにはフレームワークそのもの以外の知識も要求され、使いこなすのことは簡単ではありませんでした。また、まだまだ知らない部分や理解していないことも多く、さらなる勉強が必要であると感じています。

Androidアプリケーション開発を経験して学んだこと#ツール編

この記事について

こんにちは、Beta Computing株式会社でアルバイトをしていますAndroidアプリケーション開発担当です。
現在、Androidアプリケーションの開発を学んでおり、そこで得た知識をこの記事で書いていこうと思います。
ただし、どのようなアプリケーションを開発したかはここでは秘密保持の関係上述べることはできませんのでご了承ください。

目次

Androidアプリケーションを開発して思ったこと

Androidアプリケーションの開発において、私が感じたことを述べます。
まず、コードの書き方やツールの更新が非常に早いことが挙げられます。1、2年前に書かれた記事を参考にしても、新しい技術や流行が出現したことで、その情報が古くなってしまうことがよくありました。私は主に本を購入して技術を学んでいましたが、本は体系的に学べるメリットがある一方で、出版時点ですでに技術が古くなってしまうデメリットもあることを学びました。これらのことから、アプリケーションの開発においては、常に最新の情報を追いかけることが重要だと感じました。
また、経験からしか分からないことが多いということも感じました。インターネット上には様々なコードの書き方やツールの使い方が説明されていますが、それらの最適解を知る方法は限られています。私がアプリケーションを作る過程で、Androidアプリ開発担当の有明さんから様々な指摘を受け、多くのツールや技術を学ぶことができました。私が勉強不足だったこともありますが、普通にインターネットや書籍で調べても知ることができなかった知識がたくさんありました。これらのことから、アプリケーション開発においては、経験を積むことが重要だと感じました。 以上が、私がAndroidアプリケーション開発において感じたことです。常に最新の情報を追いかけることと、経験を積むことが重要だということが分かりました。今後も、これらのことを意識してアプリケーション開発に取り組んでいきたいと思います。

学んだこと

1. GitBucketについて

アプリケーションのバージョン管理およびコードレビューは、GitBucketと呼ばれるツールを使用して行いました。
GitBucketは、Web上でGitを管理するためのGitのホスティングサービスの1つです。
また、自分のサーバー上で管理・運営することができ、GitHubのように外部にソースコードを公開せずにWeb上でリポジトリを管理することができます。
GitBucketはオープンソースで公開されており、弊社でも社内用サーバに導入しています。

私はGitHubやGitLabを使用した経験がありましたので、少し慣れない部分もありましたが、基本的な使い方はすぐに慣れることができました。しかし、プルリクエストを立ててからマージする流れは今でも時々混乱します。
そこで、本項ではGitBucketを使用してプルリクエストを立ててからマージするまでの手順を簡単にまとめておこうと思います(リポジトリの作成やCloneなどは既に行われているものとします)。

手順1:プルリクエスト画面を開く

まずGitBucketを開くと、下のような画面になると思います。

右のメニューの中から「Pull requests」を選択します(画像の吹き出しを参考にしてください)。
次に右上の「New pull request」ボタンをクリックして新しいプルリクエストを作成していきます。

手順2:マージ先とマージするブランチを指定する

まず、ページ上部でマージ先とマージするブランチを作成します。今回はsubブランチをmainブランチにマージしたいので、下の画像のように選択します。左がマージ先のブランチで、右がマージするブランチです。

選択後は「Create pull request」ボタンをクリックして次の画面に進みます。

手順3:プルリクエストの内容を記述

ここでは、プルリクエストの内容を記述していきます。まず下の画面上部にタイトルと変更内容を記述する場所がありますので、そちらにそれぞれ記述します。 ここでは、他の方がレビューすることになるので、丁寧に書きます。

内容に問題が無ければ、右下の「Create pull request」をクリックします。

手順4:プルリクエストを確認する

無事プルリクエストを作成できれば、「pull requests」のメニューから先ほど作成したプルリクエストの詳細画面にたどり着けることができます。以下がその画面です。

ページ下のコメント欄からコメントすることも可能です。レビューする側の人はこちらから指摘事項を投稿します。

手順5:マージしてクローズする

レビューが終わり、いよいよマージすることになった場合は、先ほどの画面の真ん中にある「Merge pull request」ボタンをクリックしてマージします。
その際、コメントを書く画面に移るので、コメントを書いて「Confirm merge」ボタンをクリックしマージを確定します。
この手順を踏めば、プルリクエストは自動的にクローズされるようです。
私はよくこの手順を間違えてマージする前にクローズしたりしていました。

以上がGitBucketにおけるプルリクエストからマージまでの手順となります。
普段様々なツールを使い分けていると混乱してしまうのでこの機会に使い方整理してみました。

Git Bucketの詳しい情報は以下からご覧ください。

GitBacket詳細はこちら(GitBucket公式ページ)


2. GitHub Desktop(GUI)について

今回の開発ではGUI(グラフィカルユーザーインターフェース)であるGitHub Desktopを使用してGitを操作しました。
私がGitを勉強するために読んでいた本の内容がコマンドによる操作中心で、今までGUIには触れてきませんでしたが、勉強目的でGUIを導入してみました。
GitのGUIクライアントには色々ありますが、今回はGitHub Desktopが一番使いやすそうに見えたのでこちらの説明をします。
まずGitHub Desktopを導入して感じたことは、直感的でとてもわかりやすく、操作が簡単だということです。
以下の画像がGitHub Desktopの画面一例です。
リポジトリの選択、ブランチの選択、コミットまでクリック一つで可能です。
さらにコマンドを打つことを無く差分を確認したり(真ん中の緑の枠をご覧ください)、コミットするファイルの選択も簡単に選択することができます(ファイル右のチェックから可能です)。
しかも変更も勝手に認識されます。

もちろんプッシュもクリック一つでできます。下の画像をご覧ください(さらには、画面の中央でコミット後にプッシュできることを提案してくれますね)。

また、「History」タブを開けば、今までのコミット履歴を変更差分と共に簡単にかつ視覚的に確認することができます。下の画像をご覧ください。

ブランチの新規作成も簡単です。下の画像をご覧ください。

さらにはCherry-pickと呼ばれる他のブランチのコミットを別のブランチに適応させる操作もGitHub Desktopだとドラッグ&ドロップだけで出来てしまいます。

これには感動しました。

以上がGitHub Desktopの基本的な使い方になります。コマンドより簡単に操作が出来るため基本的にはこちらを使っていこうと思います。

GitHub Desktop詳細はこちら(GitHub Desktop公式ページ)


3. Kotlinについて

今回のAndroidアプリケーションの開発言語にはKotlinを使用しました。
私はJavaを既に学習しており、Androidアプリケーション開発においてもJavaを用いようと考えていましたが、Kotlinの方が効率的かつ安全にアプリケーションを開発できるとのアドバイスをいただき、Kotlinによる開発に挑戦することとなりました。

kotlinとは?

まずkotlinとはどのような言語でしょうか。 KotlinはJavaと互換性のある、JVM(Java仮想マシン)上で動く言語です。つまりJavaのコードはKotlinで呼び出すことが可能ですし、その逆も可能です。 また、Javaよりもコードを簡潔に書くことが出来ます。 以下はKotlinの一例です。

fun main() {
  println("Hello, world")
  // Hello, worldと出力されます
}

さらにkotlinにはnull安全性が保証されています。
null安全性は簡単にいえば、non-nullable型などを導入することでnullに関連する例外が起こらないようにしてくれるシステムです。
non-nullable型とは、nullを代入することを許さない型です。
そもそもnullを使わないようにすることで、Javaで起こりがちだったNullPointerException例外を極力排除することが出来ます(ただし入出力などでは適切にnullを扱う必要があります)。

以上がKotlinの大まかな特徴になります。KotlinはAndroidアプリケーション開発の公式言語であり、今後もよりKotlinの需要は増えていくと考えていますので、私も乗り遅れないようにKotlinについてもっと勉強しようと思います。

Kotlinについては、以下の公式サイトからさらに詳しく知ることが出来ます。

kotlin詳細(Kotlin公式サイト)

まとめ

本記事では、Androidアプリケーション開発を通して学んだことの中でもツールに関連するものをまとめました。
このような便利なツールは、知らないだけで他にもたくさんあり、自分でも探してみようと思いました。それらを使いこなして自分の力にしたいと考えています。

インターンシップに参加した感想

自己紹介

こんにちは、私は金沢大学大学院 自然科学研究科に所属しております学生です。
今回はBeta Computing株式会社のインターンシップに参加した感想をまとめます。

インターンシップに参加した理由

私が通っている大学には、イノベーション方法論という講義があります。 この講義では、さまざまな企業の方を講師として招き、実際の起業体験や起業の魅力、難しさについて学ぶことができます。 その中でBeta Computing株式会社の村松さんが講師を務められている講義があったのですが、会社説明でお話されていた少数精鋭の運営方針に非常に興味を持ちました。 一人ひとりの意見やスキルが経営戦略に直接的に反映されるため、立場に関係なく仕事やプロジェクトに大いに関与できる環境が魅力的に映りました。また、私はアプリケーションの開発にも興味があり、スマートフォンアプリケーションの開発を中心としている業務にも関心を持ち、Beta Computing社へのインターンシップの応募を決めました。

インターンシップの概要

Flutterによるアプリケーションの開発を体験しました。 具体的にはFlutterを使用するためのAndroid Studio(Androidアプリケーションを作成するためのツール)などの環境構築からFlutterを用いてBMI(体格指数)を計算し表示するアプリケーションの開発まで体験しました。
また、社員の方々とお話させていただく時間もあり、社風や制度について多くのことを知ることができました。

感想

スマートフォンアプリケーションの開発に関して、一部分実装方法やより良い設計方法などが分からないことがありましたが、Androidアプリ開発担当の有明さんから的確なアドバイスがあったおかげで、最後まで実装を終えることができたと感じました。今まで私は、Javaのプログラミングの勉強を続けており、プログラミングには自信があったのですが、実際の開発や設計に触れたことで私のスキルはまだまだ未熟であることを痛感いたしました。また、別の言い方をすれば、社員の方々の技術力がとても高度だったことに感銘を受けました。特に「こうすることでアプリケーションのパフォーマンスが上がる。」といったアドバイスから、ただ動くだけのものを作るのではなく、より良いものを作ろうとしている意識があることにプロ意識を感じました。これらのアプリケーションの開発を通して得た知識や考え方は、今後エンジニアとして働いていく上で役に立つと思いましたし、大変有益で勉強になりました。
次に、社員の方々との座談会について、代表取締役社長の吉村さんを含めて全ての方とお話しでき、Beta Computing株式会社の雰囲気をつかむことができたと感じました。また、業務や制度に関する細かな質問から、就職に対するアドバイスなどさまざまな対応をいただきました。今後の就職活動に大変有益な情報を得ることができました。この場を借りて感謝申し上げます。

最後に

本インターンシップを通じて、Beta Computing株式会社についての概要や業務・制度、日々の業務の進行方法など、幅広い体験と知識を得ることができました。 これらの経験から得たものは、将来の就職活動においても大いに役立てていきたいと思っています。

また、本インターンシップをきっかけにアルバイトとして勤務させていただくことになりました。 引き続きブログ記事をまとめていきたいと思いますので、皆様宜しくお願い致します。