メモ > 技術 > サービス: AmazonSNS > アプリ起動時にデバイストークンをサーバ側に記録
アプリ起動時にデバイストークンをサーバ側に記録
「アプリからHTTPリクエストする例」の内容も踏まえて、アプリ起動時にデバイストークンをサーバ側に記録する例。
Androidについては、ChatGPTが「OkHttp」ではなく「Volley」を使ったコードを提示してきたので、いったんそのまま採用している。
Androidの通信ライブラリの歴史を振り返る #Java - Qiita
https://qiita.com/Reyurnible/items/33049c293c70bd9924ee
※実際の実装では、さらに「デバイストークン」「サーバ側で発行した一意なID」をデバイス内に保存しておくなどの処理もあると良さそう
■サーバサイド(save_device_token.php)
<?php
header('Content-Type: application/json; charset=utf-8');
if (file_put_contents('log/' . date('YmdHis') . '.log', print_r($_POST, true)) === false) {
echo json_encode(array(
'status' => 'NG',
));
} else {
echo json_encode(array(
'status' => 'OK',
'datetime' => date('Y/m/d H:i:s'),
));
}
exit;
■アプリ(Android)
package net.refirio.pushtest1
import android.Manifest
import android.content.Context
import android.content.pm.PackageManager
import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.ManagedActivityResultLauncher
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import androidx.core.content.ContextCompat
import com.android.volley.Request
import com.android.volley.toolbox.StringRequest
import com.android.volley.toolbox.Volley
import com.google.firebase.messaging.FirebaseMessaging
import net.refirio.pushtest1.ui.theme.PushTest1Theme
class MainActivity : ComponentActivity() {
private var tokenState: MutableState<String?>? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
PushTest1Theme {
val localTokenState = remember { mutableStateOf<String?>(null) }
tokenState = localTokenState
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
MainScreen(modifier = Modifier.padding(innerPadding), localTokenState)
}
}
}
// Firebaseのトークンを取得
FirebaseMessaging.getInstance().token.addOnCompleteListener { task ->
if (!task.isSuccessful) {
Log.w("MainActivity", "Fetching FCM registration token failed", task.exception)
return@addOnCompleteListener
}
// トークンを取得
val token = task.result
Log.d("MainActivity", "FCM Token: $token")
tokenState?.value = token
// サーバにトークンを送信
sendDeviceToken(token)
}
}
private fun sendDeviceToken(token: String) {
val url = "https://example.com/app/save_device_token.php"
// URLエンコード形式のデータを作成
val postBody = "device_token=${java.net.URLEncoder.encode(token, "UTF-8")}"
// リクエストを送信
val request = object : StringRequest(
Request.Method.POST,
url,
{ response ->
// 成功時の処理
Log.d("MainActivity", "Token saved successfully: $response")
},
{ error ->
// エラー時の処理
Log.e("MainActivity", "Failed to save token: $error")
}
) {
override fun getHeaders(): Map<String, String> {
val headers = HashMap<String, String>()
headers["Content-Type"] = "application/x-www-form-urlencoded" // URLエンコード形式
return headers
}
override fun getBody(): ByteArray {
return postBody.toByteArray(Charsets.UTF_8) // ボディにエンコード済みのデータを設定
}
}
// リクエストキューに追加
Volley.newRequestQueue(this).add(request)
}
}
fun checkAndRequestPermissions(
context: Context,
permissions: Array<String>,
launcher: ManagedActivityResultLauncher<Array<String>, Map<String, Boolean>>
) {
if (
permissions.all {
ContextCompat.checkSelfPermission(
context,
it
) == PackageManager.PERMISSION_GRANTED
}
) {
// パーミッションが与えられている
Log.d("MainActivity", "Already granted")
} else {
// パーミッションを要求
launcher.launch(permissions)
}
}
@Composable
fun MainScreen(modifier: Modifier = Modifier, tokenState: MutableState<String?>) {
val context = LocalContext.current
val permissions = arrayOf(
Manifest.permission.POST_NOTIFICATIONS
)
val launcherMultiplePermissions = rememberLauncherForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { permissionsMap ->
val areGranted = permissionsMap.values.reduce { acc, next -> acc && next }
if (areGranted) {
// パーミッションが与えられている
Log.d("MainActivity", "Already granted")
} else {
// パーミッションを要求
Log.d("MainActivity", "Show dialog")
}
}
Column(modifier = modifier) {
Text("FCM Token: ${tokenState.value ?: "Loading..."}")
Button(
onClick = {
checkAndRequestPermissions(
context,
permissions,
launcherMultiplePermissions
)
}
) {
Text("通知を許可(Android13用)")
}
}
}
@Preview(showBackground = true)
@Composable
fun MainScreenPreview() {
var tokenState: MutableState<String?>? = null
val localTokenState = remember { mutableStateOf<String?>(null) }
tokenState = localTokenState
MainScreen(modifier = Modifier, localTokenState)
}
■アプリ(iOS)
import SwiftUI
struct ContentView: View {
let publisher = NotificationCenter.default.publisher(for: .deviceToken)
@State var deviceToken: String = ""
var body: some View {
VStack {
Text("DeviceToken")
TextField("Device Token is Empty", text: $deviceToken)
.padding()
}
.onReceive(publisher) { message in
if let deviceToken = message.object as? String {
sendDeviceToken(deviceToken)
}
}
}
private func sendDeviceToken(_ token: String) {
guard let url = URL(string: "https://example.com/app/save_device_token.php") else { return }
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = "device_token=\(token)".addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)?.data(using: .utf8)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error {
print("Error: \(error)")
return
}
guard let data = data else {
return
}
do {
let object = try JSONSerialization.jsonObject(with: data, options: [])
print(object)
DispatchQueue.main.async {
self.deviceToken = token
}
} catch {
print("JSON parsing error: \(error)")
}
}
task.resume()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}