Androidアプリのバックエンドを構築するには?

Back4app Androidアプリカバー

この記事では、最も人気のあるオペレーティング・システムの1つであるAndroidについてお話します。Android開発の長所と短所、モバイルアプリケーションのバックエンドの選択肢、そして独自のモバイルバックエンドを構築する方法を紹介する。

要点

  • アンドロイドの優位性アンドロイドはオープンソースのOSとして、モバイル機器の約70%を駆動し、30億人以上のアクティブユーザーがいる。
  • アプリのバックエンドオプション:モバイルアプリのバックエンドには、Infrastructure as a Service(IaaS)、Platform as a Service(PaaS)、Backend as a Service(BaaS)のいずれかを選択できる。
  • バックエンドを作る手順BaaSプラットフォームを使ってAndroidバックエンドを作る方法の詳細なチュートリアルと、ソースコードを記事の最後まで提供する。

アンドロイドとは?

アンドロイドはフリーでオープンソースのLinuxベースのオペレーティングシステムである。主にスマートフォンやタブレットなどのモバイル機器向けに設計されたが、現在ではスマートテレビ、組み込みシステム、ゲーム機などにも使われている。

アンドロイドの開発は、2003年にアンドロイド社によって開始された。同社は当初、デジタルカメラ用のOSを作ろうとしていたが、より幅広い市場に参入するため、すぐにモバイルOS開発へとシフトした。

2005年、同社は従業員とともにグーグルに買収された。アンドロイドの最初のバージョンは2008年にリリースされた。

アンドロイドはオープンソースとはいえ、ほとんどの携帯端末はグーグルの独自バージョンで動いている。このバージョンには、Google Chrome、YouTube、Google TV、Gmailなどのソフトウェアがプリインストールされている。

アンドロイドはカスタマイズの幅が広いため、多くのメーカーが自社をよりよく表現するためにアンドロイドのスキンを作っている。これが、OnePlusのAndroidがPixelのAndroidとかなり違って見える理由だ。

アンドロイドは2013年以来、最も人気のあるオペレーティング・システムである。モバイルデバイスの約70%で使用され、月間アクティブユーザー数は30億人を超える。

さらに、Google Playストアでは、300万以上のモバイル・アプリケーションにアクセスすることができる。Androidアプリのバックエンドを作成する方法については、続きをお読みください。

アンドロイド開発のメリット

では、Android向け開発の利点をいくつか探ってみよう。

クロスプラットフォーム

Android向けに開発することで、幅広いデバイスをターゲットにすることができる。携帯電話、ウェアラブル端末、スマートテレビ、ゲーム機などだ。モバイルアプリのコーディング方法を知っていれば、ウェアラブルやスマートTVアプリの開発にも問題はないだろう。

巨大な市場

先に紹介したように、Androidの市場シェアは70%と、iOSの30%よりも大きい。Androidアプリを開発することで、より幅広いユーザーへのアクセスを自動的に獲得することができます。

その上、Androidアプリは投資額が低く、投資収益率が高いことで知られている。iOSとは異なり、年間開発費もかかりません。

カスタマイズ性

アンドロイド端末は、他のオペレーティングシステムに比べてカスタマイズ性が高い。あなたが想像できるものなら、ほとんど何でも変更できる。

さらにAndroidでは、アプリを他のサードパーティ・サービスと簡単に統合することができる。

コミュニティ

Androidは、オープンソース開発者の大規模なコミュニティに支えられている。さらに、Android StudioADBLogcatのような多くの開発者に優しいツールが付属しており、アプリを簡単にコーディングすることができる。

もし行き詰まったら、GitHubStackOverflowReddit、その他のAndroid中心のコミュニティなど、助けを見つけられるプラットフォームがいくつかある。

アンドロイド開発の限界

以下は、アンドロイド開発の短所である。

セキュリティ

アンドロイドはiOSよりも安全性が低く、厳格なセキュリティ・プロトコルに従っていない。Androidはオープンソースであるため、毎週のようにセキュリティの脆弱性が検出される。そのため、ハッカーはパッチが適用される前にこれらの脆弱性を悪用することができる。

アンドロイドデバイスは、スーパーユーザーアクセスを得るためにroot化することもできる。root化はデバイスをより強力にする反面、内蔵のセキュリティ対策を無効にするため危険でもある。

複雑さ

Androidデバイスにはさまざまな形やサイズがあり、それは素晴らしいことだが、複数のデバイスタイプに対応するAndroidアプリを開発する際には大きな欠点となる。

アプリをできるだけ多くのデバイスに対応させるためには、レスポンシブ・デザインを取り入れ、異なるデバイスのハードウェアなどを考慮する必要がある。

プレミアムアプリ

プレミアムアプリは、App Storeに比べて Google Play Storeでの成功率が低い。AndroidユーザーはiOSユーザーよりもアプリにお金をかけない傾向があることは周知の事実です。プレミアム・ファーストのアプリを開発するのであれば、Androidは第二の選択肢と考えてください。

Androidアプリのバックエンドオプション

Androidアプリに最適なバックエンドとは?モバイルアプリケーションのバックエンドは、自社のインフラでホストすることも、クラウドサービスにデプロイすることもできます。モバイル・バックエンドの最も一般的なクラウド・コンピューティング・モデルには、以下のようなものがある:

  1. インフラストラクチャー・アズ・ア・サービス(IaaS)
  2. サービスとしてのプラットフォーム(PaaS)
  3. サービスとしてのバックエンド(BaaS)

各オプションは、下の画像に描かれているように、異なる抽象化レイヤーをカバーしている。

IaaS vs PaaS vs BaaS 抽象化レイヤー

バックエンドアプリケーションに最適なクラウドモデルを選択するために、各オプションを詳しく見ていこう。

インフラストラクチャー・アズ・ア・サービス(IaaS)

IaaS(Infrastructure as a Service)は、最も抽象度の低いクラウド・コンピューティング・モデルである。このモデルでは、ベンダーは高レベルのAPIや直感的なダッシュボードを通じて、サーバー、ネットワーク、ストレージ、オペレーティング・システムなどの仮想化リソースをユーザーに提供する。

IaaSの最も優れた点は、ユーザーがインフラ全体を完全にコントロールできることだ。IaaSは最も柔軟でスケーラブルなモデルだが、管理が最も難しいモデルでもある。このオプションを選択した場合、シスアドが1人か2人必要になる可能性が高い。

IaaSの例としては、Amazon Web ServicesGoogle Cloud PlatformAzureなどがある。

サービスとしてのプラットフォーム(PaaS)

PaaS(Platform as a Service)は、ユーザーがアプリケーションを開発、管理、デプロイできるように設計されたクラウド・コンピューティング・モデルである。

PaaSはインフラを提供するだけでなく、アプリケーションを開発、カスタマイズ、テストするためのユーザーフレンドリーなツールも提供する。

PaaSを活用すれば、基盤となるインフラを気にすることなく、ソフトウェアとユーザー・エクスペリエンスに集中することができる。

さらに、PaaSはアプリがオンデマンドでスケールすることを保証し、バックアップなどを引き受けてくれる。PaaSのデメリットは、コントロールレベルの低下、ベンダーロックインのリスク、比較的高いコストである。

最も人気のあるPaaSプラットフォームには、Heroku、Render、Digital Ocean App Platformなどがある。

サービスとしてのバックエンド(BaaS)

Backend as a Service(BaaS)は、本格的なバックエンド・ソリューションを提供することで、開発のバックエンド部分を自動化する。

BaaSの機能には、ユーザー管理、データベース、認証、ソーシャル統合、プッシュ通知、API、SDKなどがある。

BaaSは、IaaSとPaaSのすべての特典を提供すると同時に、追加機能を提供する。BaaSを活用するチームは、製品をより早くリリースし、エンジニアリングコストを削減し、より優れたソフトウェアを構築する傾向があります。

BaaSのデメリットとしては、コントロールやカスタマイズのレベルが低いこと、オープンソースでないプラットフォームではベンダーロックインの可能性があることなどが挙げられる。

BaaSの例としては、Back4app、AWS Amplify、Firebaseなどがある。

Androidアプリのバックエンドを作るには?

この記事では、Back4appベースのバックエンドアプリを構築し、Androidアプリケーションから接続する方法を紹介する。

前提条件

Back4appとは?

Back4appは、最新のアプリケーションのバックエンドを構築し、開発プロセスを加速するための優れたプラットフォームです。

ユーザー管理、リアルタイム・データベース、ソーシャル・インテグレーション、クラウド・コード機能、プッシュ通知、APIなど、サーバーサイドの便利な機能が充実している!

Back4appを使用することで、バックエンド作業の多くをオフソース化し、アプリケーションの重要な側面に集中し、Androidアプリのバックエンド開発を加速させることができます。

バックエンドの基盤インフラ、バックエンド・サーバー、スケーリング、メンテナンスなどに対処する必要はない。

最も重要なことは、Back4appは無料層を提供しているということです。アプリが成長すれば、月額料金でプレミアムプランにアップグレードできます。

プロジェクト紹介

この記事では、シンプルなメモアプリを作ります。このアプリでは、ユーザーがノートを追加、編集、削除できるようにする。ノートはBack4appのデータベースに保存され、Parse SDKを使って操作します。

アプリの構築にはKotlinプログラミング言語を使用します。UIには、ネイティブUIを構築するためのAndroidのモダンなツールキットであるJetpack Composeを使用します。

Back4app Android メモアプリ

Back4appアプリの作成

Androidアプリのバックエンドを開発する最初のステップとして、Back4appのアカウントが必要です。まだお持ちでない方はサインアップしてください。

Back4appアカウントにログインすると、アプリビューにリダイレクトされます。Build new app “をクリックしてアプリ作成プロセスを初期化します。

Back4app 新しいアプリを作る

Back4appプラットフォームは2つのソリューションを提供している:

  1. サービスとしてのバックエンド(BaaS) — 堅牢なバックエンド・ソリューション
  2. Containers as a Service (CaaS) — コンテナ(特にウェブアプリケーション)を管理するプラットフォーム

モバイルアプリのバックエンドを構築することを考えると、”Backend as a Service “を使うことにしよう。

Back4app BaaSソリューション

アプリに素敵で情報量の多い名前を付け、データベースとして「NoSQL」を選択し、「Create」をクリックする。これがAndroidアプリのバックエンドをデプロイするための重要なステップだ。

Back4appは、あなたのアプリケーションに必要なものを全て準備するのに少し時間がかかります。これには、データベース、ユーザー管理、スケーリング、設定などが含まれます。アプリケーションの準備ができたら、アプリケーションのデータベースビューにリダイレクトされます。

Back4appデータベースビュー

データベースの設計

続いて、データベースを設計しよう。これはAndroidアプリのバックエンドを開発するために必要なステップだ。

今回はシンプルなメモアプリを作るので、クラスは1つだけでいい。クラスを作成」をクリックし、名前を「Note」とし、「Public Read and Write」を有効にして、「クラスを作成&カラムを追加」をクリックする。

Back4app データベース作成クラス

次に、新しく作成したクラスに以下の3つのフィールドを追加します:

+-------------+-------------+--------------------+----------+
| Data type   | Name        | Default value      | Required |
+-------------+-------------+--------------------+----------+
| String      | icon        | <leave blank>      | yes      |
+-------------+-------------+--------------------+----------+
| String      | title       | <leave blank>      | yes      |
+-------------+-------------+--------------------+----------+
| String      | content     | <leave blank>      | yes      |
+-------------+-------------+--------------------+----------+

最後に、”Add row”(行を追加)ボタンをクリックし、データベースにサンプルデータを入力します。アイディアが浮かばない場合は、このデータ・ダンプをインポートすることもできます。

Back4app データベースの入力

バックエンドは以上だ。

コード・フロントエンド

このセクションでは、新しいAndroidアプリをブートストラップし、ViewModelをセットアップし、UIを実装し、Parse SDKをインストールして設定し、最後にBack4appリアルタイムデータベースからデータを取得します。

イニシャル・プロジェクト

前提条件のセクションで述べたように、以下のステップではAndroid Studioがインストールされている必要があります。まだお持ちでない場合は、ダウンロードしてください。

まずAndroid Studioを開き、”New Project “ボタンをクリックする。

Android Studioインデックス

次に、プロジェクト・テンプレートとして “Empty Activity “を選択し、”Next “をクリックします。

プロジェクトを作成するには、名前とパッケージ名を指定する必要があります。プロジェクト名にはAndroidAppを、プロジェクト・パッケージにはリバース・ドメイン名を使うことをお勧めする。

すべての設定が終わったら、”Finish “をクリックしてプロジェクトを作成する。

Android Studioプロジェクトの設定

Android Studioは、必要なすべての準備におよそ2分かかる。これには、プロジェクトのファイル構造の作成、Gradleのセットアップ、依存関係のインストールなどが含まれる。

プロジェクトの準備ができたら、エクスプローラを使ってMainActivity.ktに移動し、その内容を置き換えます:

// app/src/main/java.<your_package_name>/MainActivity.kt

package <your.package.name>

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.sp

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MaterialTheme {
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background,
                ) {
                    Text(text = "Back4app rocks!", fontSize = 18.sp)
                }
            }
        }
    }
}

必ず を実際のパッケージ名に置き換えてください。

最後に、緑色の再生ボタンをクリックするか、キーボードのShift + F10でアプリを実行してみてください。すべてがうまくいけば、エミュレータが起動し、画面にBack4app rocksのメッセージが表示されるはずです。

アンドロイド初のアプリ

ビューモデル

アプリのグローバルな状態を管理するために、ViewModelを利用する。しかし、その前にBack4appデータベースと同じ属性を持つNoteデータクラスを作成する必要がある。

Noteという名前のデータ・クラスを作成する:

// app/src/main/java.<your_package_name>/Note.kt

package <your.package.name>

data class Note(
    var objectId: String? = null,
    var icon: String,
    var title: String,
    var content: String,
) {
    companion object {
        fun generateObjectId(): String {
            val chars = ('a'..'z') + ('A'..'Z') + ('0'..'9')
            return (1..10).map { chars.random() }.joinToString("")
        }
    }
}
  1. ParseライクなオブジェクトIDを生成するために、静的なgenerateObjectId()メソッドを定義しました。
  2. objectId ==nullの場合、そのオブジェクトはまだデータベースに保存されていないことになるので、objectIdはnull可能である。

次に、以下の内容でAppViewModelクラスを作成する:

// app/src/main/java.<your_package_name>/AppViewModel.kt

package <your.package.name>

import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel

class AppViewModel : ViewModel() {
    val notes: MutableState<Map<String, Note>> = mutableStateOf(mapOf(
        "7IggsqFAKt" to Note(
            objectId = "7IggsqFAKt",
            icon = "\uD83D\uDE80",
            title = "Deploy app",
            content = "Go ahead and deploy your backend to Back4app.",
        ),
        "eFRNm0hTat" to Note(
            objectId = "eFRNm0hTat",
            icon = "\uD83C\uDFA8",
            title = "Design website",
            content = "Design the website for the conference.",
        ),
        "uC7hTQmG5F" to Note(
            objectId = "uC7hTQmG5F",
            icon = "\uD83D\uDC42",
            title = "Attend meeting",
            content = "Attend meeting with the team to discuss the conference.",
        ),
    ))

    companion object {
        @Volatile
        private var instance: AppViewModel? = null

        fun getInstance(): AppViewModel {
            return instance ?: synchronized(this) {
                instance ?: AppViewModel().also { instance = it }
            }
        }
    }
}
  1. ComposeのMutableStateを使って、データが変更されたときにUIの更新をトリガーするようにした。
  2. MutableStateはobjectIdsと Notesの Mapを保持し、これにデータを入力する。
  3. AppViewModelのインスタンスが1つだけであることを保証するために、シングルトンパターンを使用した。

AppViewModel.getInstance()を使って、アクティビティ内のAppViewModelインスタンスにアクセスできるようになりました。

主な活動

次に、MainActivity.ktの内容を以下のコードに置き換えて、ノートを表示させます:

// app/src/main/java.<your_package_name>/MainActivity.kt

package <your.package.name>

import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material3.ExtendedFloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp

class MainActivity : ComponentActivity() {
    private val viewModel = AppViewModel.getInstance()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val context = this as Context

        setContent {
            MainActivityContent(
                viewModel = viewModel,
                onNoteListItemClick = {
                    // TODO: Open note edit form
                },
                onNoteAddClick = {
                    // TODO: Open note create form
                },
            )
        }
    }
}

@Composable
fun MainActivityContent(
    viewModel: AppViewModel,
    onNoteListItemClick: (note: Note) -> Unit,
    onNoteAddClick: () -> Unit,
) {
    val notes = viewModel.notes.value
    MaterialTheme {
        Scaffold(
            topBar = { TopAppBar(title = { Text("My Notes") }) },
            floatingActionButton = {
                ExtendedFloatingActionButton(
                    onClick = { onNoteAddClick() },
                    icon = { Icon(Icons.Filled.Add, contentDescription = "Add") },
                    text = { Text("Add") }
                )
            },
        ) { contentPadding ->
            Box(modifier = Modifier.padding(contentPadding)) {
                NoteList(notes, onNoteListItemClick = { onNoteListItemClick(it) })
            }
        }
    }
}

@Composable
fun NoteListItem(note: Note, onNoteListItemClick: (note: Note) -> Unit) {
    Row(
        modifier = Modifier
            .fillMaxWidth()
            .clickable(onClick = { onNoteListItemClick(note) })
            .padding(16.dp),
        verticalAlignment = Alignment.CenterVertically,
    ) {
        Text(text = note.icon, fontSize = 32.sp, modifier = Modifier.size(48.dp))
        Spacer(modifier = Modifier.width(8.dp))
        Column {
            Text(text = note.title, fontSize = 18.sp)
            Spacer(modifier = Modifier.height(4.dp))
            Text(
                text = note.content,
                fontSize = 14.sp,
                maxLines = 1,
                overflow = TextOverflow.Ellipsis,
            )
        }
    }
}

@Composable
fun NoteList(notes: Map<String, Note>, onNoteListItemClick: (note: Note) -> Unit) {
    LazyColumn {
        items(notes.entries.toList()) { (_, note) ->
            NoteListItem(note = note, onNoteListItemClick = onNoteListItemClick)
        }
    }
}
  1. ノートが AppViewModelから取得されるようになりました。
  2. クリック・イベントをコンポーザブルで定義する代わりに、引数として渡している。
  3. UIのデザインにはComposeを使いました。よりスマートにするために、ScaffoldレイアウトにMaterialThemeを組み込みました。

今アプリを再構築すると、画面に「ハードコードされた」メモのリストが表示されるはずです。

Back4app Notesアプリ一覧

ノートフォーム活動

このセクションでは、ユーザーがノートを追加・編集できる新しいアクティビティを追加します。

まず、以下の内容でFormActivityという新しいクラスを作ります:

// app/src/main/java.<your_package_name>/FormActivity.kt

package <your.package.name>

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material3.Button
import androidx.compose.material3.ExtendedFloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.unit.dp

class FormActivity : ComponentActivity() {
    private val viewModel = AppViewModel.getInstance()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val objectId = intent.getStringExtra("objectId")
        val note = if (objectId !== null) viewModel.notes.value[objectId] else null

        setContent {
            FormActivityContent(
                note,
                onNoteAddClick = { icon: String, title: String, content: String ->
                    if (note !== null) return@FormActivityContent
                    val newNote = Note(
                        icon = icon,
                        title = title,
                        content = content,
                    )
                    newNote.objectId = Note.generateObjectId()
                    viewModel.notes.value += (newNote.objectId!! to newNote)
                    finish()
                },
                onNoteSaveClick = { icon: String, title: String, content: String ->
                    if (note === null) return@FormActivityContent
                    val updatedNote = note.copy()
                    updatedNote.icon = icon
                    updatedNote.title = title
                    updatedNote.content = content
                    viewModel.notes.value += (updatedNote.objectId!! to updatedNote)
                    finish()
                },
                onNoteDeleteClick = {
                    if (note === null) return@FormActivityContent
                    viewModel.notes.value = viewModel.notes.value.filter {
                        it.value.objectId != note.objectId
                    }
                    finish()
                },
            )
        }
    }
}

@Composable
fun FormActivityContent(
    note: Note?,
    onNoteAddClick: (icon: String, title: String, content: String) -> Unit,
    onNoteSaveClick: (icon: String, title: String, content: String) -> Unit,
    onNoteDeleteClick: () -> Unit,
) {
    MaterialTheme {
        Scaffold(
            topBar = {
                TopAppBar(title = { Text(note?.let { "Edit Note" } ?: ("Add Note")) })
            },
            floatingActionButton = {
                if (note !== null) {
                    ExtendedFloatingActionButton(
                        onClick = { onNoteDeleteClick() },
                        icon = { Icon(Icons.Filled.Delete, "Delete") },
                        text = { Text("Delete") },
                    )
                }
            },
        ) { contentPadding ->
            Box(modifier = Modifier.padding(contentPadding)) {
                NoteForm(note = note, onNoteSave = { icon, title, content ->
                    if (note === null) {
                        onNoteAddClick(icon, title, content)
                    } else {
                        onNoteSaveClick(icon, title, content)
                    }
                })
            }
        }
    }
}

@Composable
fun NoteForm(
    note: Note?,
    onNoteSave: (icon: String, title: String, content: String) -> Unit
) {
    var icon by remember { mutableStateOf(TextFieldValue(note?.icon ?: "")) }
    var title by remember { mutableStateOf(TextFieldValue(note?.title ?: "")) }
    var content by remember { mutableStateOf(TextFieldValue(note?.content ?: "")) }
    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp)
    ) {
        TextField(
            label = { Text(text = "Icon") },
            value = icon,
            onValueChange = { icon = it },
            modifier = Modifier.fillMaxWidth()
        )
        Spacer(modifier = Modifier.height(16.dp))
        TextField(
            label = { Text(text = "Title") },
            value = title,
            onValueChange = { title = it },
            modifier = Modifier.fillMaxWidth()
        )
        Spacer(modifier = Modifier.height(16.dp))
        TextField(
            label = { Text(text = "Content") },
            value = content,
            onValueChange = { content = it },
            modifier = Modifier.fillMaxWidth()
        )
        Spacer(modifier = Modifier.height(16.dp))
        Button(
            onClick = { onNoteSave(icon.text, title.text, content.text) },
            Modifier.fillMaxWidth()
        ) {
            Text(text = "Save")
        }
    }
}
  1. このフォームは、ノートの追加と編集の両方に使用されます。アクションを決定するには、objectId == nullを比較します。nullの場合は追加アクション、そうでない場合は編集アクションです。
  2. objectId !== null の場合、ステートからノートを取得し、フォームに入力します。
  3. MutableStateを正しく機能させるためには、その内容をイミュータブル(不変)として扱う必要があった。そのため、内容を変更する代わりにコピーすることもあった。
  4. コンポーズのフォームの状態は、mutableStateOf()で扱いました。

次に、AndroidManifest.xmlの アプリケーション下部にFormActivityを登録します:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <application>
        <!-- ... -->
        <activity android:name=".FormActivity"/>
    </application>

</manifest>

最後に、MainActivity.ktonNoteListItemClickonNoteAddClickの新しいIntent を作成します:

// app/src/main/java.<your_package_name>/MainActivity.kt

class MainActivity : ComponentActivity() {
    // ...

    override fun onCreate(savedInstanceState: Bundle?) {
        // ...

        setContent {
            MainActivityContent(
                viewModel = viewModel,
                onNoteListItemClick = {
                    // triggers the edit action, since `objectId` is provided
                    val intent = Intent(context, FormActivity::class.java)
                    intent.putExtra("objectId", it.objectId)
                    context.startActivity(intent)
                },
                onNoteAddClick = {
                    // triggers the add action, since no `objectId` is present
                    val intent = Intent(context, FormActivity::class.java)
                    context.startActivity(intent)
                },
            )
        }
    }
}

ファイルの一番上にインテントをインポートすることをお忘れなく:

import android.content.Intent

アプリケーションを再構築してみると、アプリケーションが動作していることに気づくだろう。このアプリでは、メモを追加したり、編集したり、削除したりすることができる。

唯一の問題は、アプリを再起動すると(「ハードコード」されたメモに)状態がリセットされてしまうことだ。

次のセクションでは、アプリをBack4appバックエンドに接続する。これにより、ステートを永続化し、複数のデバイス間で同期することができるようになる。

Androidメモアプリ編集プレビュー

Parse SDKのインストール

ご存知の通り、Back4appはParseプラットフォームに基づいています。Back4appのデータベースや一般的なBack4appを操作するには、Parse SDKをインストールする必要があります。

まずsettings.gradleに JitPackリポジトリを追加します:

// settings.gradle

pluginManagement {
    repositories {
        // ...
        maven { url "https://jitpack.io" }
    }
}
dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        // ...
        maven { url "https://jitpack.io" }
    }
}

次に、Android Parse SDKをアプリレベルのbuild.gradleに追加する:

// app/build.gradle

dependencies {
    // ...
    implementation "com.github.parse-community.Parse-SDK-Android:parse:4.2.0"
}

Gradleの設定を同期し、エラーがないことを確認する。

パースSDKの設定

Back4appのバックエンドに接続するには、Parse SDKにアプリケーションIDとクライアントキーを提供する必要があります。認証情報を取得するには、Back4appアプリに移動し、サイドバーの “App Settings > Security & Keys “を選択してください。

そして、strings.xmlに以下のように追加する:

<!-- app/src/main/res/values/strings.xml -->

<resources>
    <string name="app_name">back4app-android-app</string>
    <string name="back4app_server_url">https://parseapi.back4app.com/</string>
    <string name="back4app_app_id">your_parse_app_id</string>
    <string name="back4app_client_key">your_parse_client_key</string>
</resources>

your_parse_app_idと your_parse_client_keyをあなたの認証情報に置き換えてください。

次に、AndroidManifest.xmlを修正してインターネット・アクセスを有効にし、認証情報のメタデータを添付する:

<!-- app/src/main/AndroidManifest.xml -->

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <!-- these two permissions enable internet access -->
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.INTERNET" />

    <application>
        <!-- ... -->

        <!-- newly added metadata -->
        <meta-data
            android:name="com.parse.SERVER_URL"
            android:value="@string/back4app_server_url" />
        <meta-data
            android:name="com.parse.APPLICATION_ID"
            android:value="@string/back4app_app_id" />
        <meta-data
            android:name="com.parse.CLIENT_KEY"
            android:value="@string/back4app_client_key" />
    </application>

</manifest>

最後にParseを初期化します。アクティビティの前にParseが初期化されるように、Applicationクラスを継承したAppという新しいクラスを作成します。

App.ktを作成し、次のようにParseを初期化する:

// app/src/main/java/<your_package_name>/App.kt

package <your.package.name>

import android.app.Application
import com.parse.Parse

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

        Parse.initialize(
            Parse.Configuration.Builder(this)
                .applicationId(getString(R.string.back4app_app_id))
                .clientKey(getString(R.string.back4app_client_key))
                .server(getString(R.string.back4app_server_url))
                .build()
        )
    }
}

次に、AndroidManifest.xmlに Appクラスを登録する:

<!-- app/src/main/AndroidManifest.xml -->

<application
    android:name=".App"
    ...
>
    <!-- ... -->
</application>

もう一度アプリを再構築し、Logcatウィンドウにエラーがないか確認してください。エラーがなければ、Back4appへの接続は成功しています。

Back4appのメモを読み込む

アプリが完成する前にしなければならない最後のことは、Back4appデータベースからノートをロードし、Parseの実際の使い方を見ることです。

まずAppViewModelに移動します。マップからデータを削除し、initブロックを追加します:

// app/src/main/java/<your_package_name>/AppViewModel.kt

package <your.package.name>

import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel

class AppViewModel : ViewModel() {
    val notes: MutableState<Map<String, Note>> = mutableStateOf(mapOf())

    init {
        val query = com.parse.ParseQuery.getQuery<com.parse.ParseObject>("Note")
        query.orderByDescending("createdAt")
        query.findInBackground { notes, e ->
            if (e == null) {
                for (parseNote in notes) {
                    val note = Note(
                        objectId = parseNote.objectId,
                        icon = parseNote.getString("icon")!!,
                        title = parseNote.getString("title")!!,
                        content = parseNote.getString("content")!!,
                    )
                    this.notes.value += (note.objectId!! to note)
                }
            } else {
                println("Error: ${e.message}")
            }
        }
    }

    companion object {
        // ...
    }
}

このコードでは、Parse SDKを使用してデータベースからノートを取得し、ノート・データ・クラスに変換してマップに保存します。

次に、以下の3つのメソッドをNoteに追加する:

// app/src/main/java/<your_package_name>/Note.kt

package <your.package.name>

import com.parse.ParseObject
import com.parse.ParseQuery

data class Note(
    var objectId: String? = null,
    var icon: String,
    var title: String,
    var content: String,
) {
    fun addToParse(callback: (objectId: String) -> Unit) {
        if (objectId !== null) throw Exception("Note is already saved to Parse!")

        val parseNote = ParseObject("Note")
        parseNote.put("icon", icon)
        parseNote.put("title", title)
        parseNote.put("content", content)

        parseNote.saveInBackground {
            if (it !== null) throw Exception("Error: ${it.message}")
            objectId = parseNote.objectId
            callback(parseNote.objectId)
        }
    }

    fun updateToParse(callback: (objectId: String) -> Unit) {
        if (objectId === null) throw Exception("Note hasn't been saved to Parse yet!")

        val query = ParseQuery.getQuery<ParseObject>("Note")
        val parseNote = query.get(objectId)
        parseNote.put("icon", icon)
        parseNote.put("title", title)
        parseNote.put("content", content)

        parseNote.saveInBackground {
            if (it !== null) throw Exception("Error: ${it.message}")
            callback(parseNote.objectId)
        }
    }

    fun deleteFromParse(callback: () -> Unit) {
        if (objectId === null) throw Exception("Note hasn't been saved to Parse yet!")

        val query = ParseQuery.getQuery<ParseObject>("Note")
        val parseNote = query.get(objectId)
        parseNote.deleteInBackground {
            if (it !== null) throw Exception("Error: ${it.message}")
            callback()
        }
    }
}
  1. addToParse() は、新しいノートをその詳細とともに Parse サーバーに追加します。成功した場合は、そのノートの一意な識別子を保存し、完了したら通知します。
  2. updateToParse() は、Parse サーバー上の既存のノートの情報を更新します。成功した場合は、変更後に完了のシグナルを送ります。
  3. deleteFromParse() は、保存されたノートを Parse サーバから削除します。成功すれば、削除が完了したことを確認します。

最後に、FormActivityのクリックメソッドを修正して、新しく定義したメソッドを呼び出すようにします:

// app/src/main/java/<your_package_name>/NoteFormActivity.kt

class FormActivity : ComponentActivity() {
    // ...

    override fun onCreate(savedInstanceState: Bundle?) {
        // ...

        setContent {
            FormActivityContent(
                note,
                onNoteAddClick = { icon: String, title: String, content: String ->
                    if (note !== null) return@FormActivityContent
                    val newNote = Note(
                        icon = icon,
                        title = title,
                        content = content,
                    )
                    newNote.addToParse {
                        viewModel.notes.value += (it to newNote)
                        finish()
                    }
                },
                onNoteSaveClick = { icon: String, title: String, content: String ->
                    if (note === null) return@FormActivityContent
                    val updatedNote = note.copy()
                    updatedNote.icon = icon
                    updatedNote.title = title
                    updatedNote.content = content
                    updatedNote.updateToParse {
                        viewModel.notes.value += (it to updatedNote)
                        finish()
                    }
                },
                onNoteDeleteClick = {
                    if (note === null) return@FormActivityContent
                    viewModel.notes.value = viewModel.notes.value.filter {
                        it.value.objectId != note.objectId
                    }
                    note.deleteFromParse {
                        finish()
                    }
                },
            )
        }
    }
}

それだけだ!

これでアプリケーションは完全に動作し、Back4appバックエンドと同期しました。データを変更してアプリを動かしてみて、データベースビューに変更が反映されているか確認してみてください。

結論

この記事では、モバイルバックエンドを構築し、Androidアプリから接続することに成功しました。バックエンドにはBack4appのBaaSソリューションを利用し、フロントエンドにはJetpack ComposeとKotlinを利用した。

ここまでで、あなたはモバイルバックエンドがどのように機能するかをきちんと理解し、自分で構築できるようになっているはずだ。最終的なソースコードはback4app-android-apprepoにある。


Leave a reply

Your email address will not be published.