Skip to content

Conversation

@JohnZoellerG
Copy link
Contributor

Reverts #179

This reverts commit b6c6980.
@gemini-code-assist
Copy link

Summary of Changes

Hello @JohnZoellerG, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request serves as a revert, bringing back the shrinewear module into the project. This module is a self-contained Wear OS sample application designed to illustrate modern authentication practices on wearable devices. It showcases the integration of the Android Credential Manager for streamlined sign-in experiences with Passkeys, passwords, and Google accounts, while also providing alternative authentication paths via OAuth and legacy Google Sign-in. The restoration encompasses all necessary code, build configurations, and documentation to make the sample fully functional and informative.

Highlights

  • Reintroduction of Wear OS Module: This pull request fully restores the shrinewear module, which is a comprehensive Wear OS sample application, effectively undoing a previous deletion.
  • Credential Manager Integration: The restored module includes a robust implementation of the Android Credential Manager API, demonstrating authentication flows using Passkeys, traditional Passwords, and Sign-in With Google.
  • Legacy Authentication Fallbacks: The sample provides fallback mechanisms for authentication, including OAuth 2.0 device authorization grant flow and legacy Google Sign-in, for scenarios where Credential Manager is dismissed or unavailable.
  • Modern Android Development Stack: The module leverages Jetpack Compose for its user interface, Kotlin for application logic, OkHttp for networking, and integrates Horologist libraries for Wear OS-specific UI components and Google Sign-in.
  • Comprehensive Documentation: A detailed readme.md file is included within the wear module, offering extensive guidance on the sample's functionality, prerequisites, and step-by-step instructions for setting up and testing various authentication methods.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@JohnZoellerG JohnZoellerG merged commit 7fa9207 into main Oct 28, 2025
4 checks passed
Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request reverts the deletion of the shrinewear module, effectively re-introducing a significant amount of code for a Wear OS sample application. My review focuses on treating this as new code. I've identified several issues, including a critical file with incorrect content that will prevent compilation, a couple of high-severity issues that could lead to app crashes due to unhandled exceptions, and several medium-severity issues related to code quality, use of deprecated APIs, and incomplete documentation. Addressing these points will improve the stability and maintainability of the sample app.

Comment on lines +1 to +23
/*
* Copyright 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.webkit.webviewsample.theme

import androidx.compose.ui.graphics.Color

val Purple200 = Color(0xFFBB86FC)
val Purple500 = Color(0xFF6200EE)
val Purple700 = Color(0xFF3700B3)
val Teal200 = Color(0xFF03DAC5)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

This file has incorrect content and package name. It appears to contain color definitions from a different sample (com.google.webkit.webviewsample.theme) instead of the NetworkException class that is used elsewhere in the code. This will cause a compilation failure.

/*
 * Copyright 2025 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.authentication.shrinewear.network

/**
 * Custom exception for network-related errors.
 */
class NetworkException(message: String) : Exception(message)

wear-compose-foundation = { module = "androidx.wear.compose:compose-foundation", version.ref = "wearComposeMaterial3" }
wear-compose-material3 = { module = "androidx.wear.compose:compose-material3", version.ref = "wearComposeMaterial3" }
wear-compose-navigation = { module = "androidx.wear.compose:compose-navigation", version.ref = "wearComposeMaterial3" }
wear-compose-ui-tooling = { group = "androidx.wear.compose", name = "compose-ui-tooling"}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The wear-compose-ui-tooling dependency is missing a version. This will likely cause build failures. You should add a version reference, probably wearComposeMaterial3 which is used by other wear.compose artifacts in this file.

Suggested change
wear-compose-ui-tooling = { group = "androidx.wear.compose", name = "compose-ui-tooling"}
wear-compose-ui-tooling = { group = "androidx.wear.compose", name = "compose-ui-tooling", version.ref = "wearComposeMaterial3"}

Comment on lines +38 to +41
buildConfigField(
"String", "GOOGLE_SIGN_IN_SERVER_CLIENT_ID",
"\"PASTE_YOUR_SERVER_CLIENT_ID_HERE\""
)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The GOOGLE_SIGN_IN_SERVER_CLIENT_ID has a placeholder value. While this is expected in a sample, the accompanying readme.md file does not instruct the developer to replace it. This is crucial for the Google Sign-In functionality to work. Please update the documentation to include this step.

Comment on lines +189 to +190
federatedSessionId = federationOptions.sessionId
?: throw IllegalStateException("Session ID was null in server response")

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Throwing an IllegalStateException when the session ID is null from the server response can cause the application to crash. It's better to handle this case gracefully by logging an error and returning false, which is consistent with how other failures are handled in this class.

Suggested change
federatedSessionId = federationOptions.sessionId
?: throw IllegalStateException("Session ID was null in server response")
federatedSessionId = federationOptions.sessionId
if (federatedSessionId == null) {
Log.e(TAG, "Session ID was null in server response for federation options")
signOut()
return false
}

Comment on lines +94 to +95
val currentSessionId = sessionId
?: throw IllegalStateException("Requested Passkey was not provided with a valid session.")

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Throwing an IllegalStateException when the session ID is null can cause the app to crash, as the sessionId parameter is nullable. It would be safer to handle this case gracefully, for example by returning an error result or throwing a more specific, documented exception that can be caught upstream.

@@ -0,0 +1,141 @@
/

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

There is a stray / character at the beginning of the file. It should be removed.

Comment on lines +81 to +120
object DemoInstructionsState {
var isFirstLaunch: Boolean = true
}

/**
* Displays an [AlertDialog] containing introductory demo instructions for the user.
*
* This dialog is shown upon the initial launch of the application and can be dismissed
* by the user.
*
* Note: The `AlertDialog` API used here (`edgeButton` and `visible`) might be from an
* older or specific alpha version of `androidx.wear.compose.material3`. For newer
* versions, consider using `confirmButton` and `dismissButton` for actions, and
* conditionally rendering the dialog using an `if` statement.
*/
@Composable
private fun DemoInstructions() {
if (!DemoInstructionsState.isFirstLaunch) {
return
}

AlertDialog(
visible = true,
onDismissRequest = {
DemoInstructionsState.isFirstLaunch = false
},
edgeButton = {
AlertDialogDefaults.EdgeButton(onClick = {
DemoInstructionsState.isFirstLaunch = false
})
},
title = {
Text(
text = stringResource(R.string.shrine_sample),
textAlign = TextAlign.Center,
)
},
text = { Text(stringResource(R.string.see_readme_md_for_usage_directions)) },
)
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

There are a couple of areas for improvement in the DemoInstructions composable:

  1. The DemoInstructionsState object uses a global mutable var to track if the dialog has been shown. This is not a robust solution as the state will be reset on process death, causing the dialog to reappear unexpectedly. A better approach is to use rememberSaveable within the composable to persist this state.
  2. The visible parameter of AlertDialog is deprecated. The dialog should be conditionally composed based on a state variable.

Here's a suggested refactoring that addresses both points.

@Composable
private fun DemoInstructions() {
    var showDialog by rememberSaveable { mutableStateOf(true) }

    if (showDialog) {
        AlertDialog(
            onDismissRequest = { showDialog = false },
            edgeButton = {
                AlertDialogDefaults.EdgeButton(onClick = { showDialog = false })
            },
            title = {
                Text(
                    text = stringResource(R.string.shrine_sample),
                    textAlign = TextAlign.Center,
                )
            },
            text = { Text(stringResource(R.string.see_readme_md_for_usage_directions)) },
        )
    }
}

Comment on lines +50 to +56
AlertDialog(
visible = showDialog,
onDismissRequest = { signOut() },
edgeButton = { AlertDialogDefaults.EdgeButton(onClick = { signOut() }) },
title = { Text(text = "You are now signed in", textAlign = TextAlign.Center) },
text = { Text("To sign out and start over, tap the check mark") },
)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The visible parameter of AlertDialog is deprecated. The recommended practice is to conditionally compose the dialog based on a state variable rather than controlling its visibility with this parameter.

    if (showDialog) {
        AlertDialog(
            onDismissRequest = { signOut() },
            edgeButton = { AlertDialogDefaults.EdgeButton(onClick = { signOut() }) },
            title = { Text(text = "You are now signed in", textAlign = TextAlign.Center) },
            text = { Text("To sign out and start over, tap the check mark") },
        )
    }

} catch (e: CancellationException) {
throw e
} catch (e: Exception) {
e.printStackTrace()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Calling e.printStackTrace() is discouraged in production code. It's better to use Android's logging framework, like Log.e(TAG, "...", e), to ensure exceptions are properly logged and can be collected by tools like Logcat. This should be fixed in all catch blocks in this file (lines 114, 170, 191).

Suggested change
e.printStackTrace()
Log.e(TAG, "Failed to retrieve verification info", e)

Comment on lines +196 to +252
/**
* Simple implementation of a POST request. Normally you'd use a library to do these requests.
*/
private suspend fun doPostRequest(
url: String,
params: Map<String, String>,
dispatcher: CoroutineDispatcher = Dispatchers.IO,
): JSONObject {
return withContext(dispatcher) {
val conn = (URL(url).openConnection() as HttpURLConnection)
val postData = StringBuilder()
for ((key, value) in params) {
if (postData.isNotEmpty()) postData.append('&')
postData.append(URLEncoder.encode(key, "UTF-8"))
postData.append('=')
postData.append(URLEncoder.encode(value, "UTF-8"))
}
val postDataBytes = postData.toString().toByteArray(charset("UTF-8"))

conn.apply {
requestMethod = "POST"
setRequestProperty("Content-Type", "application/x-www-form-urlencoded")
setRequestProperty("Content-Length", postDataBytes.size.toString())
doOutput = true
outputStream.write(postDataBytes)
}

val inputReader = BufferedReader(InputStreamReader(conn.inputStream, "UTF-8"))
val response = inputReader.readText()

Log.d(TAG, DEBUG_POST_REQUEST_UTIL_RESPONSE.format(response))

JSONObject(response)
}
}

/**
* Simple implementation of a GET request. Normally you'd use a library to do these requests.
*/
private suspend fun doGetRequest(
url: String,
requestHeaders: Map<String, String>,
dispatcher: CoroutineDispatcher = Dispatchers.IO,
): JSONObject {
return withContext(dispatcher) {
val conn = (URL(url).openConnection() as HttpURLConnection)
requestHeaders.onEach { (key, value) ->
conn.setRequestProperty(key, value)
}
val inputReader = BufferedReader(InputStreamReader(conn.inputStream, "UTF-8"))
val response = inputReader.readText()

Log.d(TAG, DEBUG_REQUEST_UTIL_RESPONSE.format(response))

JSONObject(response)
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This ViewModel implements doPostRequest and doGetRequest using the low-level HttpURLConnection. Since the project already includes OkHttp as a dependency (used in AuthNetworkClient), it would be better to use OkHttp here as well for consistency and to leverage its features like connection pooling, interceptors, and a simpler API. This would reduce boilerplate code and make the networking logic more robust and maintainable.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant