One of the best things about Android development is how easy it is to ship apps for different experiences like Android TV, Android Auto and Wear OS(smartwatches). We all know that providing a seamless experience is crucial for retaining our users and on some occasions it's a great idea to take advantage of the integration between phone and watch.
In this tutorial, we'll walk through the process of synergizing your Android app with Wear OS creating a channel for transferring data.
1 - Install Smartwatch Wear OS by Google
The first step is to install this app(Smartwatch Wear OS by Google) in your emulator.
It is necessary to access some Wear OS exclusive libs and for pairing the Wear OS emulator.
Be aware that you'll need a Google account to access the Play Store.
2 - Pair the emulated devices
After creating both the phone and watch emulator head over to Device Manager in Android Studio make sure they're both running and select the Pair Wearable
option in the More Actions(three vertical dots) dropdown.
3 - Create/Add Wear OS Application module in Android Studio
The steps:
File
New
New Project
Select the Wear OS template
Select Empty Wear App
Check Pair with Empty Phone app
Finish
4 - Create the view model on your wear module
Now create the view model that'll be responsible for receiving the picture taken by the app's phone module and loading it on our Wear OS device.
class MainDataViewModel(
application: Application
) :
AndroidViewModel(application),
DataClient.OnDataChangedListener {
var image by mutableStateOf<Bitmap?>(null)
private set
private var loadPhotoJob: Job = Job().apply { complete() }
@SuppressLint("VisibleForTests")
override fun onDataChanged(dataEvents: DataEventBuffer) {
dataEvents.forEach { dataEvent ->
when (dataEvent.type) {
DataEvent.TYPE_CHANGED -> {
when (dataEvent.dataItem.uri.path) {
IMAGE_PATH -> {
loadPhotoJob.cancel()
loadPhotoJob = viewModelScope.launch {
image = loadBitmap(
DataMapItem.fromDataItem(dataEvent.dataItem)
.dataMap
.getAsset(IMAGE_KEY)
)
}
}
}
}
}
}
}
private suspend fun loadBitmap(asset: Asset?): Bitmap? {
if (asset == null) return null
val response =
Wearable.getDataClient(getApplication<Application>()).getFdForAsset(asset).await()
return response.inputStream.use { inputStream ->
withContext(Dispatchers.IO) {
BitmapFactory.decodeStream(inputStream)
}
}
}
companion object {
const val IMAGE_PATH = "/image"
const val IMAGE_KEY = "photo"
}
}
We need to implement DataClient.OnDataChangedListener to keep an eye on any data received from the phone. After receiving the data we unload it as an image to further displaying it.
5 - Create the layout and set up the activity for displaying the picture in the Wear module
class MainActivity : ComponentActivity() {
private val dataClient by lazy { Wearable.getDataClient(this) }
private val mainDataViewModel by viewModels<MainDataViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MainApp(image = mainDataViewModel.image)
}
}
override fun onResume() {
super.onResume()
dataClient.addListener(mainDataViewModel)
}
override fun onPause() {
super.onPause()
dataClient.removeListener(mainDataViewModel)
}
}
@Composable
fun MainApp(
image: Bitmap?
) {
val scalingLazyListState = rememberScalingLazyListState()
Scaffold(
vignette = { Vignette(vignettePosition = VignettePosition.TopAndBottom) },
positionIndicator = { PositionIndicator(scalingLazyListState = scalingLazyListState) },
timeText = { TimeText() }
) {
Box(
modifier = Modifier
.fillMaxWidth()
.aspectRatio(1f)
.padding(32.dp)
) {
if (image == null) {
Image(
painterResource(id = R.drawable.photo_placeholder),
contentDescription = stringResource(
id = R.string.placeholder
),
modifier = Modifier.fillMaxSize()
)
} else {
Image(
image.asImageBitmap(),
contentDescription = stringResource(
id = R.string.received_from_phone
),
modifier = Modifier.fillMaxSize()
)
}
}
}
}
6 - Create the view model in your phone's module
class MainViewModel :
ViewModel() {
var image by mutableStateOf<Bitmap?>(null)
private set
fun onPictureTaken(bitmap: Bitmap?) {
image = bitmap ?: return
}
}
7 - Create the activity in your phone's module
class MainActivity : ComponentActivity() {
private val dataClient by lazy { Wearable.getDataClient(this) }
private val isCameraSupported by lazy {
packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)
}
private val mainViewModel by viewModels<MainViewModel>()
private val takePhotoLauncher = registerForActivityResult(
ActivityResultContracts.TakePicturePreview()
) { bitmap ->
mainViewModel.onPictureTaken(bitmap = bitmap)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MaterialTheme {
MainApp(
image = mainViewModel.image,
isCameraSupported = isCameraSupported,
onTakePhotoClick = ::takePhoto,
onSendPhotoClick = ::sendPhoto
)
}
}
}
private fun takePhoto() {
if (!isCameraSupported) return
takePhotoLauncher.launch(null)
}
private fun sendPhoto() {
lifecycleScope.launch {
try {
val image = mainViewModel.image ?: return@launch
val imageAsset = image.toAsset()
val request = PutDataMapRequest.create(IMG_PATH).apply {
dataMap.putAsset(IMAGE_KEY, imageAsset)
}
.asPutDataRequest()
.setUrgent()
val result = dataClient.putDataItem(request).await()
Log.d(TAG, "PutDataItem result: $result")
} catch (exception: Exception) {
Log.d(TAG, "PutDataItem exception: $exception")
}
}
}
private suspend fun Bitmap.toAsset(): Asset =
withContext(Dispatchers.Default) {
ByteArrayOutputStream().use { byteStream ->
compress(Bitmap.CompressFormat.PNG, 100, byteStream)
Asset.createFromBytes(byteStream.toByteArray())
}
}
companion object {
private const val TAG = "MainActivity"
private const val IMG_PATH = "/image"
private const val IMAGE_KEY = "photo"
}
}
In the sendPhoto()
function we retrieve the captured picture stored in the view model and build the request with PutDataMapRequest.create
passing the path and key to be retrieved in the Wear OS module.
The statement dataClient.putDataItem(request).await()
is responsible for sending the image as a request.
8 - Run
Now run each module in its compatible emulated device, take a picture on your phone hit send and watch as the captured is displayed in the Wear OS emulator.
And congratulations you have fully created an in-app connection between Android and Wear OS.