/ kyokomi note / blog

Uaalでプロセス間通信を使ってFirebaseAuthのidTokenを取得する

August 29, 2020 [Android | Unity | Firebase]

UaalでNative側で認証したFirebaseAuthのidTokenを取得する で別プロセスをやめたことによって問題が起きたので、プロセス間通信を試してみた。

起きた問題

やったこと

イメージ図

img

Messengerを使ったプロセス間通信について

https://developer.android.com/guide/components/bound-services?hl=ja#Messenger より

実装

Manifestで別processにする

// AndroidManifest.xml

...

    <service
      android:name=".service.MessengerService"
      android:enabled="true"
      android:exported="false"/>

    <activity
      android:name=".ui.sub.SubActivity"
      android:process=":Unity" /> <!-- 別processにする -->
      
...

Activityは何もしなくていい(Fragemtnでやる)

// SubActivity.kt
package dev.kyokomi.uaal.android.ui.sub

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import dev.kyokomi.uaal.android.R

class SubActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.sub_activity)
        if (savedInstanceState == null) {
            supportFragmentManager.beginTransaction()
                .replace(R.id.container, SubFragment.newInstance())
                .commitNow()
        }
    }
}

onStartでbindServiceし、buttonをclickするとMessageをsendする

// SubFragment.kt
package dev.kyokomi.uaal.android.ui.sub

import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.os.*
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.Toast
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import dev.kyokomi.uaal.android.R
import dev.kyokomi.uaal.android.service.MSG_SAY_HELLO
import dev.kyokomi.uaal.android.service.MessengerService

class SubFragment : Fragment() {
    /** Messenger for communicating with the service.  */
    private var mService: Messenger? = null
    private var replyToService: Messenger? = null

    /** Flag indicating whether we have called bind on the service.  */
    private var bound: Boolean = false

    internal class IncomingHandler(
        context: Context,
        private val applicationContext: Context = context.applicationContext
    ) : Handler() {
        override fun handleMessage(msg: Message) {
            when (msg.what) {
                MSG_SAY_HELLO -> {
                    Toast.makeText(applicationContext, "reply!", Toast.LENGTH_SHORT).show()
                    Log.d(TAG, msg.data.getString("idToken") ?: "<No Token>")
                }
                else -> super.handleMessage(msg)
            }
        }
    }

    private val mConnection = object : ServiceConnection {

        override fun onServiceConnected(className: ComponentName, service: IBinder) {
            mService = Messenger(service)
            bound = true
        }

        override fun onServiceDisconnected(className: ComponentName) {
            mService = null
            bound = false
        }
    }

    fun sayHello() {
        if (!bound) return
        // Create and send a message to the service, using a supported 'what' value
        val msg: Message = Message.obtain(null, MSG_SAY_HELLO, 0, 0)
        msg.replyTo = replyToService
        try {
            mService?.send(msg)
        } catch (e: RemoteException) {
            e.printStackTrace()
        }
    }

    ...
}
// SubFragment.kt
    
    ...

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        val root = inflater.inflate(R.layout.sub_fragment, container, false)

        replyToService = Messenger(IncomingHandler(requireContext()))

        val button: Button = root.findViewById(R.id.button4)
        button.setOnClickListener {
            sayHello()
        }

        return root
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        viewModel = ViewModelProvider(this).get(SubViewModel::class.java)
    }

    override fun onStart() {
        super.onStart()
        // Bind to the service
        if (!bound) {
            Intent(context, MessengerService::class.java).also { intent ->
                requireActivity().bindService(intent, mConnection, Context.BIND_AUTO_CREATE)
            }
        }
    }

    override fun onStop() {
        super.onStop()
        // Unbind from the service
        if (bound) {
            requireActivity().unbindService(mConnection)
            bound = false
        }
    }
}
// MessengerService.kt
package dev.kyokomi.uaal.android.service

import android.app.Service
import android.content.Context
import android.content.Intent
import android.os.Handler
import android.os.IBinder
import android.os.Message
import android.os.Messenger
import android.util.Log
import android.widget.Toast
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.google.firebase.auth.FirebaseAuth

/** Command to the service to display a message  */
const val MSG_SAY_HELLO = 1

// https://developer.android.com/guide/components/bound-services?hl=ja#Messenger
class MessengerService : Service() {
    companion object {
        private val TAG = MessengerService::class.java.simpleName
    }

    /**
     * Target we publish for clients to send messages to IncomingHandler.
     */
    private lateinit var mMessenger: Messenger

    /**
     * Handler of incoming messages from clients.
     */
    internal class IncomingHandler(
        context: Context,
        private var firebaseAuthIdToken: LiveData<String>,
        private val applicationContext: Context = context.applicationContext,
    ) : Handler() {
        override fun handleMessage(msg: Message) {
            when (msg.what) {
                MSG_SAY_HELLO -> {
                    Toast.makeText(applicationContext, "hello!", Toast.LENGTH_SHORT).show()
                    if (msg.replyTo != null) {
                        val replyMes = Message.obtain(null, MSG_SAY_HELLO, 0, 0)
                        replyMes.data.putString("idToken", firebaseAuthIdToken.value)
                        msg.replyTo.send(replyMes)
                    }
                }
                else -> super.handleMessage(msg)
            }
        }
    }

    private var firebaseAuthIdToken = MutableLiveData<String>()

    /**
     * When binding to the service, we return an interface to our messenger
     * for sending messages to the service.
     */
    override fun onBind(intent: Intent): IBinder? {
        Toast.makeText(applicationContext, "binding", Toast.LENGTH_SHORT).show()

        // FirebaseAuthのidTokenを取得してliveDataに設定しておく
        FirebaseAuth.getInstance().addIdTokenListener { firebaseAuth: FirebaseAuth ->
            Log.d(TAG, "firebase:addIdTokenListener")
            firebaseAuth.currentUser?.getIdToken(false)?.addOnSuccessListener {
                firebaseAuthIdToken.value = it.token ?: ""
                Log.d(TAG, "firebase:${firebaseAuthIdToken}")
            }
        }

        // idTokenのLiveDataをIncomingHandlerにわたしておく
        mMessenger = Messenger(IncomingHandler(this, firebaseAuthIdToken))
        return mMessenger.binder
    }
}

所感

last modified September 26, 2020

👋 Related posts in the uaal series...