![見出し画像](https://assets.st-note.com/production/uploads/images/164258892/rectangle_large_type_2_1db0a7ea32c63f40d1a4a0a06087396f.png?width=1200)
Androiderとして久々のアプリ開発に感じた楽しさと挑戦
はじめに
はじめまして、英語学習アプリmikanでAndroidエンジニアをしているikkunです。
こちらはmikan Advent Calendar 2024の4日目の記事になります!
3日目は弊チームでインターンとして手伝ってくれているrichakoさんが「Androidエンジニアの私がiOSのプロジェクトにコミットした話」を書いてくれています。ぜひご一読ください!
noteを書くのは人生で2度目で、最初は3年前に書いたmikanの入社エントリです。早くも3年が過ぎているのを考えると、人生の短さを感じますね。
元記事の通り入社当初はmikanのBackendチームでサーバーサイドの開発に関わったのち、昨年8月にAndroidチームに移りました。
前職でもiOS, Androidのモバイルアプリ開発には携わりましたが、数年間はAndroidに触れていないブランク期間があり、一抹の不安もありつつのjob changeでした。
今のAndroid開発に1年ほど携わってみて、実際どうだったか?今後どうしていきたいか?を語りつつ、来年への抱負で締めたいと思います。
久々のAndroid開発はどう?
私は元々mikanに転職する以前はAndroidの開発を2年ほど経験していましたが、当時はまだ主流だったAndroid Viewでの手続き的なUI開発を採用していて、MVCアーキテクチャで組んでいました。
久々にAndroid開発に入り、まず感動したのが(モバイルエンジニアやフロントエンドエンジニアの方には今更かもしれませんが)宣言的UIのメリットの高さでした。
UIの柔軟さ、開発効率の高さ等のメリットは見聞きしていましたが、実際に業務で触った結果、作りたいものに対して実現できるスピード感が確かに大きく違うなという実感があります。
おかげでキャッチアップ速度も短縮できたのではと思います。
例えば、Android開発に加わって最初に「お知らせ機能」というサービス側からの通知機能を実装しました。具体的には下記のようなよくあるお知らせ画面です。
![](https://assets.st-note.com/img/1732901237-7Qq68p0soEJgTym34VFvPGcU.jpg?width=1200)
こちらを素直に今までのAndroid Viewの構成で書くと、ざっくり下記になると思います。
<!-- activity_announcement.xml -->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<!-- ツールバー -->
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:title="@string/toolbar_title_announcement"
app:navigationIcon="@drawable/ic_back" />
<!-- 区切り線 -->
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@android:color/darker_gray" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone" />
<!-- 空データ表示用 -->
<TextView
android:id="@+id/empty_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/no_announcements"
android:visibility="gone" />
</LinearLayout>
class AnnouncementActivity : AppCompatActivity() {
val viewModel: AnnouncementViewModel by viewModels()
private lateinit var recyclerView: RecyclerView
private lateinit var emptyView: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_announcement)
// ツールバー設定
val toolbar: Toolbar = findViewById(R.id.toolbar)
setSupportActionBar(toolbar)
toolbar.setNavigationOnClickListener {
onBackPressedDispatcher.onBackPressed()
}
// RecyclerView設定
recyclerView = findViewById(R.id.recycler_view)
recyclerView.layoutManager = LinearLayoutManager(this)
// 空データ表示用の設定
emptyView = findViewById(R.id.empty_view)
// データ取得とUI更新
viewModel.uiState.observe(this) { uiState ->
if (uiState.announcements.isEmpty()) {
showEmptyView()
} else {
showAnnouncements(uiState.announcements)
}
}
}
private fun showEmptyView() {
recyclerView.visibility = View.GONE
emptyView.visibility = View.VISIBLE
}
private fun showAnnouncements(announcements: List<Announcement>) {
emptyView.visibility = View.GONE
recyclerView.visibility = View.VISIBLE
}
}
これがJetpack Composeによる実装だと下記となり、人間のメンタルモデルに合ったリーダブルな実装になっていると感じます。
@Composable
fun AnnouncementScreen(
viewModel: AnnouncementViewModel,
navIconClickListener: () -> Unit,
onClickCellItem: (Announcement) -> Unit
) {
Scaffold(
modifier = Modifier.fillMaxSize(),
topBar = {
// ツールバー設定
Column {
TopAppBar(
title = {
Text(
text = stringResource(R.string.toolbar_title_announcement),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
},
navigationIcon = {
IconButton(onClick = {
navIconClickListener.invoke()
}) {
Icon(
painter = painterResource(id = R.drawable.ic_back),
contentDescription = null
)
}
},
modifier = Modifier.fillMaxWidth(),
elevation = 0.dp,
backgroundColor = Color.White
)
// 区切り線
Divider(modifier = Modifier.fillMaxWidth())
}
},
backgroundColor = Color.White
) {
// UIの状態に応じてコンテンツを切り替え
if (viewModel.uiState.announcements.isEmpty()) {
// 空データ用の表示 (Android Viewの empty_view に対応)
AnnouncementEmptyComposable()
} else {
// 通知リストの表示 (Android Viewの recycler_view に対応)
LazyColumn {
itemsIndexed(items = viewModel.uiState.announcements) { index, item ->
AnnouncementCellComposable(item, index, onClickCellItem)
}
}
}
}
}
状態の変化に応じた自動的な再描画もJetpack Composeならではですが、状態管理とUI構築の責務切り分けがしやすいことで、かなり保守性が高まっている印象です。(Android View版、Jetpack Compose版はいずれもイメージ実装であることをご承知おきください。)
5年以上前、前職でiOSでカレンダー用の横スクロール画面を組んだことがあったのですが
controller上の縦スクロールのUITableViewを強引に90°回転させて横表示させる
同じくcontroller上で表示中かどうかの判定により、表示領域のハイライト等の状態を手続きなコードで切り替える
というような事をやっていたのを思い出しました。
もちろんiOS, Androidの差異はあるにせよ、宣言的UIも含めモダンな技術スタックによる開発効率の高さに感動しました。
(前職を退く前にリファクタプロジェクトが走り、当時の90°回転版UITableViewは無事消すことができたので安心して退けました。)
また、Android Leadエンジニアをしている @gumioji の手厚いオンボーディングに加え、ドキュメントがしっかり残っていることもスムーズに開発に入るうえで大きな助けとなりました。
若干古い内容が残っていることもありますが、とりわけ必要に応じて参照されるドキュメントは都度アップデートもされやすいですし、ドキュメンテーション文化が適切にワークしていると感じます。
![](https://assets.st-note.com/img/1732897478-Swa19ErodTiOzvXLnHNeD74K.png?width=1200)
![](https://assets.st-note.com/img/1732897487-N47aypDA8g3SxO9GhiPKRwBj.png?width=1200)
今後どうしていきたい?
英語学習アプリmikan、特にAndroidアプリはまだまだ発展途上です。
開発面ではKotlin 100%達成に加え、Jetpack Composeへの移行も進んでいますが、ここからさらに「Androidならではの、直感的な手触り感のあるアプリ」を作っていくことが、ますます多くのユーザーに価値を届けることに繋がると考えています。
具体的にはユーザーにとってポイントとなるインタラクションには良いタイミングでhapticフィードバックを入れたり、より幅広いユーザーに使っていただけるようアクセシビリティに注力したり。アプリに触れた時に、そうした「ああ、直感的だ」と思ってもらえるような満足度の高いユーザー体験を、来年には何か1つでも作れればと考えています。
特に2024年前半はデザインチームと合同でGoogle推奨のMaterial Design勉強会をしましたが、そこでの学びを実際のアプリに還元したい気持ちがあります。
来年の抱負ですが、事業issueを引き続き推進しつつ、Androidならではのユーザー体験をさらに幅広いユーザーさんに届けられるよう、1つでも多くアクセシビリティの向上に取り組められればと思います。
さいごに
いかがだったでしょうか。
久々のAndroid開発どうだった?というこの1年を振り返った若干個人的な内容になりましたが、普段アプリを書いていない方にも「最近のAndroid開発はそういう感じなんだ〜」と雰囲気だけでも知ってもらえると嬉しいです。
最後ですが、現在この英語アプリmikanを一緒に作っていける方を探しております!
この記事を読んで「mikan、なんだか気になる」と思ってくれた方、いずれのポジションでも採用を強化しているので是非気軽にご連絡ください。
明日はコンテンツチームの kakki が記事を書いてくれます。お楽しみに!