Reputation: 13
I am trying to write a companion app for my Wear OS watch face to help install the watch face if the user is browsing on their Android phone. I have been able to create a simple UI with my desired text, but I am really struggling to implement RemoteActivityHelper to push a Play Store link from the phone to the watch. I have looked at several examples, but none of them seem to transfer easily to my app. In my latest attempt, I took this snippet from Google's reference documentation, which they state should accomplish exactly what I am trying to do:
val remoteActivityHelper = RemoteActivityHelper(context, executor)
val result = remoteActivityHelper.startRemoteActivity(
Intent(Intent.ACTION_VIEW)
.setData(
Uri.parse("http://play.google.com/store/apps/details?id=com.example.myapp"))
.addCategory(Intent.CATEGORY_BROWSABLE),
nodeId)
But Android Studio protests, and 'context' and 'executor' and 'Uri' and 'nodeID' throw errors. I think I can resolve the Uri error by importing android.net.Uri. I still have unresolved references for context, executor, and nodeID. Context Actions are not helpful (to me). My gradle file implements libs.androidx.wear.remote.interactions which I think is what is required for RemoteActivityHelper. Obviously I need to update the URI for my app....
I'm sorry for such a newbie question, I'm just really at a loss here. Can anyone help me with this functionality, preferably in Kotlin?
Opening the app on Android phone should open an install link on watch face. I have tried several code examples but none of them are accepted by Android Studio.
EDITED: Below, see the full code for my MainActivity.kt. I merged the previous answer with my code and most of it seems, ok. I just have one hang-up, and that is related to my Install Button. The answer's code was written in Java and then converted to Kotlin, relying on the XML layout. Since I am starting from the ground up and trying to learn latest standards, I wrote most of this in Kotlin, using Jetpack Compose for the UI. The original answer had a button with an onClick listener, and that button was identified with findViewById. Jetpack Compose uses @Composable functions for UI elements. The variables necessary for the onClick action (e.g., NodeId) are not accessible inside the button function. If I leave the code where it is, I cannot reference the button because there is no XML id code for the button.
package com.watchfacestudio.companionapptemplate
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Email
import androidx.compose.material3.Icon
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.wear.remote.interactions.RemoteActivityHelper
import com.google.android.gms.tasks.Task
import com.google.android.gms.tasks.Tasks
import com.google.android.gms.wearable.Node
import com.google.android.gms.wearable.Wearable
import com.watchfacestudio.companionapptemplate.ui.theme.CompanionAppTemplateTheme
import java.util.concurrent.Executors
class MainActivity : ComponentActivity() {
// Global variable that will hold the node that corresponds to the watch
private var WATCH_NODE: Node? = null
private val uri: String = "com.watchfacestudio.sedona" //TODO: Add watchface market uri
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
CompanionAppTemplateTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = Color.White
) {
MainContent()
// Creates a thread
Thread {
// Get all nodes (nodes are devices connected)
val nodeListTask: Task<List<Node>> = Wearable.getNodeClient(applicationContext).connectedNodes
try {
// Try to get the first node and assign it to the WATCH_NODE variable | Usually the first node is the watch, but, if you want, you can iterate all nodes (check google docs to see how to identify the watch)
WATCH_NODE = Tasks.await(nodeListTask)[0]
} catch (ignore: Exception) {}
}.start()
// Looks for the button id (change the button_id_here for the button id you created) and adds a click listener
findViewById<android.view.View>(InstallButton()).setOnClickListener { _ ->
// When the user clicks this button, all this code here runs
// Creates the intent that we will pass to the watch
val i = Intent(Intent.ACTION_VIEW)
i.addCategory(Intent.CATEGORY_BROWSABLE)
// Creates the uri to the watchface/app | the string part is a standard deeplink to google play | Important: the watchface/watch app and the phone app, must have the same package name!!
// Possible alternative to remove need to manually specifying package name:
// i.setData(Uri.parse("market://details?id=" + getPackageName()))
i.data = Uri.parse("https://play.google.com/store/apps/details?id=" + uri)
// Create a remoteActivityHelper instance
val remoteActivityHelper = RemoteActivityHelper(this, Executors.newSingleThreadExecutor())
// Send the intent to the watch (node)
remoteActivityHelper.startRemoteActivity(i, WATCH_NODE?.id ?: "")
// Just shows a message on the user phone
Toast.makeText(this, "Check your watch", Toast.LENGTH_SHORT).show()
}
}
}
}
}
@Composable
fun MainContent() {
Column (
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.Top,
horizontalAlignment = Alignment.CenterHorizontally
){
Image(
painter = painterResource(R.drawable.developer_icon_transparent_small),
contentDescription = null,
modifier = Modifier
.size(100.dp)
.padding(bottom = 16.dp)
)
Text(
stringResource(R.string.Greeting),
modifier = Modifier
.padding(bottom= 16.dp)
)
Text(
stringResource(R.string.Instructions),
modifier = Modifier
.padding(bottom = 16.dp)
)
Text(
stringResource(R.string.Contact),
modifier = Modifier
.padding(bottom = 16.dp)
)
InstallButton()
EmailButton()
}
}
@Composable
fun InstallButton() {
OutlinedButton(onClick = {
}) {
Text("Install on Watch")
}
}
@Composable
fun EmailButton() {
val context = LocalContext.current
Row (
modifier = Modifier.padding(0.dp),
verticalAlignment = Alignment.CenterVertically
){
Icon(imageVector = Icons.Outlined.Email, contentDescription = null)
TextButton(onClick = {
context.sendMail(
to = "[email protected]",
subject = "Watch Face install"
)
}) {
Text(text = "[email protected]")
}
}
}
fun Context.sendMail(to: String, subject: String) {
try {
val intent = Intent(Intent.ACTION_SEND)
intent.type = "vnd.android.cursor.item/email" // or "message/rfc822"
intent.putExtra(Intent.EXTRA_EMAIL, arrayOf(to))
intent.putExtra(Intent.EXTRA_SUBJECT, subject)
startActivity(intent)
} catch (e: ActivityNotFoundException) {
// TODO: Handle case where no email app is available
} catch (t: Throwable) {
// TODO: Handle potential other type of exceptions
}
}
@Preview(showBackground = true)
@Composable
fun Preview() {
CompanionAppTemplateTheme {
MainContent()
}
}}
Upvotes: 0
Views: 231
Reputation: 563
Take a look on this this answer. It is in Java, but I think it can help you a bit, is the code I used on my companions.
About the errors, I might be wrong, but I think you are just copying and pasting the code... if so, you should change the variables.
context: would become "this"
executor: is basically a thread to run the commands on different times. Code also on the link I mentioned.
Uri: yes, you should import "android.net.Uri"
nodeID: is the device you are sending the command to. All devices connected to you phone (called node) has a nodeID. The link I mentioned before has a code to get all nodesID.
Ps.: Dont just open the link on the watch whenever the phone app is launched. Add a button so the user can decide when to launch the store on the watch.
Edited:
I don't know much kotlin syntax, so I converted the the full class online for you... maybe it helps. (there might be syntax errors! but the logic is correct, in case there are syntax errors check the other answer [in java / no syntax erros there lol])
package ENTER_THE_PACKAGE_HERE
import androidx.appcompat.app.AppCompatActivity
import androidx.wear.remote.interactions.RemoteActivityHelper
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.widget.Toast
import com.google.android.gms.tasks.Task
import com.google.android.gms.tasks.Tasks
import com.google.android.gms.wearable.Node
import com.google.android.gms.wearable.Wearable
import java.util.concurrent.Executors
class Main : AppCompatActivity() {
// Global variable that will hold the node that corresponds to the watch
private var WATCH_NODE: Node? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main)
// Creates a thread
Thread {
// Get all nodes (nodes are devices connected)
val nodeListTask: Task<List<Node>> = Wearable.getNodeClient(applicationContext).connectedNodes
try {
// Try to get the first node and assign it to the WATCH_NODE variable | Usually the first node is the watch, but, if you want, you can iterate all nodes (check google docs to see how to identify the watch)
WATCH_NODE = Tasks.await(nodeListTask)[0]
} catch (ignore: Exception) {}
}.start()
// Looks for the button id (change the butto_id_here for the button id you created) and adds a click listener
findViewById<android.view.View>(R.id.button_id_here).setOnClickListener { _ ->
// When the user clicks this button, all this code here runs
// Creates the intent that we will pass to the watch
val i = Intent(Intent.ACTION_VIEW)
i.addCategory(Intent.CATEGORY_BROWSABLE)
// Creates the uri to the watchface/app | the string part is a standard deeplink to google play | Important: the watchface/watch app and the phone app, must have the same package name!!
i.data = Uri.parse("market://details?id=$packageName")
// Create a remoteActivityHelper instance
val remoteActivityHelper = RemoteActivityHelper(this, Executors.newSingleThreadExecutor())
// Send the intent to the watch (node)
remoteActivityHelper.startRemoteActivity(i, WATCH_NODE?.id ?: "")
// Just shows a message on the user phone
Toast.makeText(this, "Check your watch", Toast.LENGTH_SHORT).show()
}
}
}
Upvotes: 0