Nearby Connections在MVVM架构下的问题及多Activity连接咨询
Hey there, let's break down your questions one by one since you're working on a quiz game with Nearby Connections + MVVM/LiveData/Room—super solid tech stack, by the way!
1. Why does using Application Context in ViewModel throw ApiException: 13?
First, while Nearby.getConnectionsClient() accepts any Context, the advertise() method has hidden dependencies on a foreground Activity context that Application Context can't provide:
- Nearby Connections often triggers system-level UI flows during advertising (like pairing confirmation popups or permission prompts). Application Context has no associated window or foreground lifecycle, so it can't handle these UI interactions, leading to the
ERROR 13(a subtype ofERROR_ADVERTISE_FAILEDrelated to missing foreground support). - Google Play Services' binding logic for Nearby also ties into the Context's lifecycle. Application Context is global and detached from Activity lifecycle events, which can cause state mismatches for the ConnectionsClient.
Fixes to keep logic out of Activity:
- Use ViewModel + LifecycleObserver: Have your ViewModel observe the Activity's lifecycle, grab the Activity context only when it's in a foreground state (like
onResume) to runadvertise(), and clean up connections when the Activity pauses/destroys. - Build a singleton ConnectionManager: Create a global manager that holds a
WeakReference<Activity>to avoid memory leaks, and only execute foreground-dependent operations when an active Activity context is available.
2. Do you need to restart advertising after screen rotation?
Yes, but you can make it seamless:
- When the Activity is destroyed by rotation, the ConnectionsClient tied to the old Activity context will be cleaned up, so advertising stops automatically.
- Use your ViewModel (which survives rotation) to persist the "is advertising" state. When the new Activity instance is created, have the ViewModel trigger a restart of advertising using the new Activity context.
- If you use the singleton ConnectionManager mentioned above, it can automatically restart advertising once it gets a reference to the new, active Activity context.
3. How to manage Nearby connections across multiple Activities?
The cleanest approach is a global ConnectionManager singleton that encapsulates all Nearby logic, with lifecycle-aware hooks to share the connection between your Lobby and Game Activities. Here's how to implement it:
Step 1: Create the singleton ConnectionManager
class ConnectionManager private constructor(private val appContext: Context) { private var connectionsClient: ConnectionsClient? = null private var currentActivityRef: WeakReference<Activity>? = null private val messageListeners = mutableListOf<OnMessageReceivedListener>() // Singleton instance companion object { @Volatile private var INSTANCE: ConnectionManager? = null fun getInstance(appContext: Context): ConnectionManager { return INSTANCE ?: synchronized(this) { INSTANCE ?: ConnectionManager(appContext.applicationContext).also { INSTANCE = it } } } } // Initialize client with active Activity context fun attachActivity(activity: Activity) { currentActivityRef = WeakReference(activity) connectionsClient = Nearby.getConnectionsClient(activity) } // Start advertising (with context check) fun startAdvertising(serviceId: String, callback: AdvertisingStatusCallback) { currentActivityRef?.get()?.let { activity -> connectionsClient?.advertise( activity, "QuizGameHost", object : ConnectionLifecycleCallback() { // Implement your connection lifecycle logic here }, AdvertisingOptions(Strategy.P2P_STAR) )?.addOnSuccessListener { callback.onAdvertiseStarted() } ?.addOnFailureListener { callback.onAdvertiseFailed(it) } } ?: callback.onAdvertiseFailed(IllegalStateException("No active Activity context")) } // Register/unregister message listeners for different Activities fun registerMessageListener(listener: OnMessageReceivedListener) { messageListeners.add(listener) } fun unregisterMessageListener(listener: OnMessageReceivedListener) { messageListeners.remove(listener) } // Example interface for callbacks interface AdvertisingStatusCallback { fun onAdvertiseStarted() fun onAdvertiseFailed(e: Exception) } interface OnMessageReceivedListener { fun onMessageReceived(endpointId: String, message: ByteArray) } }
Step 2: Use the manager in your Activities
Lobby Activity:
class LobbyActivity : AppCompatActivity() { private lateinit var connectionManager: ConnectionManager override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) connectionManager = ConnectionManager.getInstance(applicationContext) connectionManager.attachActivity(this) // Start advertising when the lobby loads connectionManager.startAdvertising("com.your.quizgame.service", object : ConnectionManager.AdvertisingStatusCallback { override fun onAdvertiseStarted() { // Update UI to show advertising is active } override fun onAdvertiseFailed(e: Exception) { // Handle error (show toast, retry, etc.) } }) } override fun onDestroy() { super.onDestroy() // Clean up if needed connectionManager.stopAdvertising() } }
Game Activity:
class GameActivity : AppCompatActivity(), ConnectionManager.OnMessageReceivedListener { private lateinit var connectionManager: ConnectionManager override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) connectionManager = ConnectionManager.getInstance(applicationContext) connectionManager.attachActivity(this) connectionManager.registerMessageListener(this) } override fun onMessageReceived(endpointId: String, message: ByteArray) { // Handle game messages (like quiz questions/answers) } override fun onDestroy() { super.onDestroy() connectionManager.unregisterMessageListener(this) } }
This setup keeps all Nearby logic centralized, avoids memory leaks (thanks to WeakReference), and lets you seamlessly share the connection between Activities.
内容的提问来源于stack exchange,提问作者Jakub Kracina

