-
Notifications
You must be signed in to change notification settings - Fork 613
Fix #6006: Add Installation ID with copy option to App Version screen #6073
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR adds an Installation ID display with a copy button to the App Version screen to help users share their installation ID for debugging and support purposes.
Changes:
- Added Installation ID display section with copy functionality to the App Version screen
- Implemented copy state tracking using a Handler with 2-second reset delay
- Added three new string resources for Installation ID label, explanation, and copy button
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 7 comments.
| File | Description |
|---|---|
| app/src/main/res/values/strings.xml | Added three new string resources for Installation ID UI labels and descriptions |
| app/src/main/res/layout/app_version_fragment.xml | Restructured layout to add Installation ID section above existing app version information, including copy button and info icon |
| app/src/main/java/org/oppia/android/app/administratorcontrols/appversion/AppVersionViewModel.kt | Added Installation ID retrieval, copy functionality with timed state reset, and supporting LiveData/ObservableField properties |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| android:background="@color/component_color_shared_screen_tertiary_background_color" | ||
| android:fillViewport="true"> |
Copilot
AI
Jan 18, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The android:fillViewport attribute is only applicable to ScrollView and is not valid for ConstraintLayout. This attribute should be removed as it has no effect on ConstraintLayout and may cause confusion.
| android:background="@color/component_color_shared_screen_tertiary_background_color" | |
| android:fillViewport="true"> | |
| android:background="@color/component_color_shared_screen_tertiary_background_color"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@brijesh025, PTAL.
app/src/main/java/org/oppia/android/app/administratorcontrols/appversion/AppVersionViewModel.kt
Outdated
Show resolved
Hide resolved
| ).toLiveData().observe(fragment) { result -> | ||
| if (result is AsyncResult.Success) { | ||
| isInstallationIdCopied.set(true) | ||
| handler.postDelayed({ | ||
| isInstallationIdCopied.set(false) | ||
| }, COPY_ICON_RESET_DELAY_MS) | ||
| } else { | ||
| oppiaLogger.w( | ||
| "AppVersionViewModel", | ||
| "Encountered unexpected non-successful result when copying to clipboard: $result" | ||
| ) | ||
| } | ||
| } |
Copilot
AI
Jan 18, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The LiveData observer added to the clipboard operation result is never removed. This creates a potential memory leak because the observer will continue to exist even after the fragment is destroyed. The fragment reference passed to observe() means the observer is tied to the fragment lifecycle, but since this is called within a function that can be invoked multiple times, you may accumulate multiple observers. Consider using observeForever() with proper cleanup in onCleared(), or better yet, return the result as a LiveData that the UI layer observes directly rather than observing within the ViewModel.
| /** Indicates whether the installation ID was recently copied. */ | ||
| val isInstallationIdCopied = ObservableField(false) | ||
|
|
||
| /** The device installation ID. */ | ||
| val installationId: LiveData<String?> by lazy { | ||
| Transformations.map( | ||
| loggingIdentifierController.getInstallationId().toLiveData() | ||
| ) { result -> | ||
| (result as? AsyncResult.Success)?.value | ||
| } | ||
| } | ||
|
|
||
| /** Copies the specified installation ID (if non-null) to the user's clipboard. */ | ||
| fun copyInstallationId(installationId: String?) { | ||
| installationId?.let { | ||
| val appName = resourceHandler.getStringInLocale(R.string.app_name) | ||
| clipboardController.setCurrentClip( | ||
| resourceHandler.getStringInLocaleWithWrapping( | ||
| R.string.learner_analytics_device_id_clipboard_label_description, appName | ||
| ), | ||
| installationId | ||
| ).toLiveData().observe(fragment) { result -> | ||
| if (result is AsyncResult.Success) { | ||
| isInstallationIdCopied.set(true) | ||
| handler.postDelayed({ | ||
| isInstallationIdCopied.set(false) | ||
| }, COPY_ICON_RESET_DELAY_MS) |
Copilot
AI
Jan 18, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The implementation approach for tracking copy state differs from the existing pattern used in DeviceIdItemViewModel and ProfileLearnerIdItemViewModel. Those implementations use clipboardController.getCurrentClip() to track the copied state rather than using a Handler with a timed reset. This creates an inconsistent user experience where the Installation ID copy button resets after 2 seconds, while other copy buttons in the learner analytics section stay in the "copied" state until a different ID is copied. Consider following the existing pattern by tracking currentCopiedId via the clipboard controller for consistency.
| /** Indicates whether the installation ID was recently copied. */ | ||
| val isInstallationIdCopied = ObservableField(false) | ||
|
|
||
| /** The device installation ID. */ | ||
| val installationId: LiveData<String?> by lazy { | ||
| Transformations.map( | ||
| loggingIdentifierController.getInstallationId().toLiveData() | ||
| ) { result -> | ||
| (result as? AsyncResult.Success)?.value | ||
| } | ||
| } | ||
|
|
||
| /** Copies the specified installation ID (if non-null) to the user's clipboard. */ | ||
| fun copyInstallationId(installationId: String?) { | ||
| installationId?.let { | ||
| val appName = resourceHandler.getStringInLocale(R.string.app_name) | ||
| clipboardController.setCurrentClip( | ||
| resourceHandler.getStringInLocaleWithWrapping( | ||
| R.string.learner_analytics_device_id_clipboard_label_description, appName | ||
| ), | ||
| installationId | ||
| ).toLiveData().observe(fragment) { result -> | ||
| if (result is AsyncResult.Success) { | ||
| isInstallationIdCopied.set(true) | ||
| handler.postDelayed({ | ||
| isInstallationIdCopied.set(false) | ||
| }, COPY_ICON_RESET_DELAY_MS) | ||
| } else { | ||
| oppiaLogger.w( | ||
| "AppVersionViewModel", | ||
| "Encountered unexpected non-successful result when copying to clipboard: $result" | ||
| ) | ||
| } | ||
| } | ||
| } | ||
| } |
Copilot
AI
Jan 18, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new Installation ID display and copy functionality lacks test coverage. The existing AppVersionActivityTest does not include tests for the new Installation ID UI element, the copy button behavior, or the state changes when copying. Consider adding tests to verify: 1) Installation ID is displayed correctly, 2) Copy button is enabled when ID is available, 3) Copy button updates its state when clicked, and 4) Copied state resets after the delay period.
adhiamboperes
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for making the previously requested changes, @brijesh025! I have left a few more comments, and please add tests in AppVersionActivityTest.
| android:background="@color/component_color_shared_screen_tertiary_background_color" | ||
| android:fillViewport="true"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@brijesh025, PTAL.
| handler.postDelayed({ | ||
| isInstallationIdCopied.set(false) | ||
| }, COPY_ICON_RESET_DELAY_MS) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
postDelayed is a prohibited method, please see:
oppia-android/scripts/assets/file_content_validation_checks.textproto
Lines 455 to 467 in 8dd48de
| file_content_checks { | |
| file_path_regex: ".+?\\.kt$" | |
| prohibited_content_regex: ".+?\\.(post|postDelayed)[\\s]*(\\(|\\{).*?" | |
| failure_message: "Prefer avoiding post() and postDelayed() methods as they can can lead to subtle and difficult-to-debug crashes. Prefer using LifecycleSafeTimerFactory for most cases when an operation needs to run at a future time. For cases when state needs to be synchronized with a view, use doOnPreDraw or doOnLayout instead. For more context on the underlying issue, see: https://betterprogramming.pub/stop-using-post-postdelayed-in-your-android-views-9d1c8eeaadf2." | |
| exempted_file_name: "app/src/main/java/org/oppia/android/app/drawer/NavigationDrawerFragmentPresenter.kt" | |
| exempted_file_name: "app/src/main/java/org/oppia/android/app/player/state/StateFragmentPresenter.kt" | |
| exempted_file_name: "app/src/main/java/org/oppia/android/app/topic/info/TopicInfoFragmentPresenter.kt" | |
| exempted_file_name: "app/src/main/java/org/oppia/android/app/utility/ClickableAreasImage.kt" | |
| exempted_file_name: "scripts/src/javatests/org/oppia/android/scripts/regex/RegexPatternValidationCheckTest.kt" | |
| exempted_file_name: "utility/src/main/java/org/oppia/android/util/parser/image/UrlImageParser.kt" | |
| # These are false positives. | |
| exempted_file_name: "data/src/test/java/org/oppia/android/data/backends/gae/testing/FeedbackReportingServiceTestOrchestratorTest.kt" | |
| exempted_file_name: "data/src/test/java/org/oppia/android/data/backends/gae/testing/PlatformParameterServiceTestOrchestratorTest.kt" |
I do not think it is needed here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok, i have used
lifecycleSafeTimerFactory.createTimer(COPY_ICON_RESET_DELAY_MS).observe(fragment) {
isInstallationIdCopied.set(false)
}
in place of
handler.postDelayed({
isInstallationIdCopied.set(false)
}, COPY_ICON_RESET_DELAY_MS)
|
Hi @brijesh025, it looks like some changes were requested on this pull request by @adhiamboperes. PTAL. Thanks! |
|
Hello, hopping in here to ask what the hex value of the green text is. It looks a bit bright. I'm sure it's the provided hex but want to double check (output can always look a bit different) thanks so much!! |
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
|
We discussed this PR in the CLaM meeting this week and collected some feedback for you @brijesh025. It'd be nice to to refine the UI to be a bit more streamlined, and also consistent for copy functionality and among other settings screens. In particular, please make the following changes:
Does this all make sense? It should largely clean up the UI, I think. |
@swnmn I think that is #3bd1c4 (
But we're always happy to look them up, too, since tying these IDs back to the UI isn't easy. Looks like a bright teal. This has been used for a few years now with dark mode, but it's fortunately very easy for us to change it if that's something the design team would like to consider. |
Thanks for the detailed feedback, @benhemming! |
|
@BenHenning as this is the color we've been using for now, I'd rather approach the update on a larger scale in the near future. For now, I think it looks okay. My general thinking is that it's a bit brighter than the rest of the color palette and can feel disconnected for the user. |
|
@BenHenning is there a particular way I can create a ticket for this without it getting lost? It would be for design to explore the text color in the near future for dark mode. Last time I made a ticket it disappeared and Sean had to help find it, so want to be sure it doesn't get lost. Appreciate the help! |
|
Hi @swnmn, could you be referring to the design team repo? Once an issue is created there, we can add the link to the corresponding issue on the android repo. |
|
@BenHenning |
|
Unassigning @brijesh025 since a re-review was requested. @brijesh025, please make sure you have addressed all review comments. Thanks! |
|
Thanks @brijesh025! Some thoughts:
App Version:
Installation ID:
Some rewording to the version info to be a bit more correct and clearer, and I reversed the onus for the install ID to make it less actionable. The only other thing is I don't totally love the way the layout looks, though it is much better than before and probably reasonable for a tertiary user flow like this. I'm curious if you have any specific suggestions for improvements, though, @swnmn. Finally, please update screenshots in the main PR description once these latest changes are addressed @brijesh025 since I think we'll probably be happy to take them once any other code-related findings are also addressed in subsequent reviews. |
|
@BenHenning I’ve added the App Version heading as you suggested. Earlier, I thought the heading might be redundant, which is why I hadn’t included it. Could you please check the main header on the App Version screen as well and let me know if that looks fine? |
|
Thanks @brijesh025! I think it is a bit redundant for tablet view, but I think it's okay and looks good on the mobile view. I have no other other concerns on the UI elements. I defer to @adhiamboperes on the code review and @swnmn for any follow-up design suggestions. |
|
Hi @BenHenning let's make note of it. We're working on the Android masterfile, after which I'd love to do an evaluation of where we are today and what screens make sense to refine and test (of course, after we address any backlogged priority tickets from users). |
Sounds good @swnmn. I've filed oppia/design-team#212 to track. |
|
Hi @BenHenning, Any updates regarding this PR? If any review or changes are required from my side, please let me know. |




Explanation
Fixes #6006: Adds the Installation ID to the App Version screen and provides a copy button so users can easily share the ID for debugging and support purposes.
Essential Checklist
Screenshots
Tablet
Light mode

Dark mode

Phone
Light mode

Dark mode

Demo Video
oppia_pr1_update.mp4
Note: That toast at bottom showing oppia pasted from clipboard.... is not implemented in app, it is android feature as far I have researched, since I have not implemented any toast feature.
Testing
##Files changed : 4