Skip to content
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

Migrate to ViewPager2 #2873

Merged
merged 17 commits into from
Dec 20, 2024
Merged

Migrate to ViewPager2 #2873

merged 17 commits into from
Dec 20, 2024

Conversation

devanshhooda
Copy link
Contributor

@devanshhooda devanshhooda commented Oct 17, 2024

Summary

Poc to fix: SAAS-16035

Product Description

Removed the usage of Duolingo's rtl viewpager as the repository is archived and no longer holds support.
It was observed that the issue was reproducible only in release mode while working fine in debug mode hence we suspected that it might be because outdated viewpager dependency.

In terms of UX, the behaviour almost the same as it was with RtlViewPager, the most notable difference is that when swiping through the tabs the active page doesn't change, only when a tab is selected:

Case Detail

viewpager2_demo.mov

Case Details in landscape mode on large screens

inline_tile_large_screen.mov

Inline Tile

inline_tile.mov

Safety Assurance

  • If the PR is high risk, "High Risk" label is set
  • I have confidence that this PR will not introduce a regression for the reasons below
  • Do we need to enhance manual QA test coverage ? If yes, "QA Note" label is set correctly

Note for QA

This PR brings changes to the tab system used by case details and inline tiles, see videos above. It's important during QA to see if the tabs are working as expected and that right-to-left (RTL) is still supported.

@devanshhooda devanshhooda added the skip-integration-tests Skip android tests. label Oct 17, 2024
@devanshhooda devanshhooda requested a review from avazirna October 17, 2024 06:25
@devanshhooda devanshhooda self-assigned this Oct 17, 2024
@devanshhooda devanshhooda marked this pull request as ready for review October 25, 2024 15:14
@avazirna avazirna changed the base branch from master to commcare_2.55 December 17, 2024 00:11
@avazirna avazirna changed the title viewpager2 Poc with issues [Do not merge] Migrate to ViewPager2 Dec 17, 2024
Copy link

coderabbitai bot commented Dec 17, 2024

📝 Walkthrough

Walkthrough

The pull request introduces a comprehensive refactoring of the detail view and tab management in an Android application. The changes primarily focus on migrating from older Android UI components to more modern alternatives, specifically replacing RtlViewPager with ViewPager2 and introducing TabLayout.

The modifications span multiple files across the project, including build configuration, layout resources, and Java source files. Key changes include removing deprecated drawable resources for tab styling, updating the app/build.gradle to adjust dependency management, and restructuring how tabs and viewpagers are implemented in the EntityDetailActivity and TabbedDetailView classes.

The build configuration now includes a multiDexKeepProguard configuration, suggesting an optimization in how the application handles multiple DEX files. The layout changes introduce a more standardized approach to tab management, with the new TabLayout providing enhanced flexibility and modern design patterns.

The adapter for detail views has been updated to use the newer FragmentStateAdapter, which requires changes in method signatures and constructor parameters. These modifications align the codebase with more recent Android development practices and component libraries.

Sequence Diagram

sequenceDiagram
    participant User
    participant EntityDetailActivity
    participant TabbedDetailView
    participant EntityDetailPagerAdapter
    participant TabLayout
    participant ViewPager2

    User->>EntityDetailActivity: Open Detail View
    EntityDetailActivity->>TabbedDetailView: Initialize View
    TabbedDetailView->>TabLayout: Create Tab Layout
    TabbedDetailView->>ViewPager2: Initialize ViewPager2
    TabbedDetailView->>EntityDetailPagerAdapter: Create Adapter
    EntityDetailPagerAdapter-->>TabbedDetailView: Provide Fragments
    TabbedDetailView->>TabLayout: Link with ViewPager2
    TabLayout->>ViewPager2: Synchronize Tabs
    User->>TabLayout: Select Tab
    TabLayout->>ViewPager2: Change Page
    ViewPager2->>EntityDetailPagerAdapter: Load Fragment
Loading

This sequence diagram illustrates the new flow of tab and view management, highlighting the interactions between the key components in the updated implementation.

Tip

CodeRabbit's docstrings feature is now available as part of our Early Access Program! Simply use the command @coderabbitai generate docstrings to have CodeRabbit automatically generate docstrings for your pull request. We would love to hear your feedback on Discord.


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🧹 Nitpick comments (1)
app/src/org/commcare/adapters/EntityDetailPagerAdapter.java (1)

32-32: Consider annotating additional constructor parameters with @NonNull

Currently, only fm and lifecycle are annotated with @NonNull. If detail, detailIndex, reference, and modifier are also expected to be non-null, consider annotating them with @NonNull to enhance null safety and ensure consistency.

Apply this diff to add @NonNull annotations:

 public EntityDetailPagerAdapter(@NonNull FragmentManager fm, @NonNull Lifecycle lifecycle,
-                                Detail detail, int detailIndex, TreeReference reference, ListItemViewModifier modifier) {
+                                @NonNull Detail detail, int detailIndex, @NonNull TreeReference reference, @NonNull ListItemViewModifier modifier) {
     super(fm, lifecycle);
     this.detail = detail;
     this.detailIndex = detailIndex;
     this.mEntityReference = reference;
     this.modifier = modifier;
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4238469 and 5e0945e.

📒 Files selected for processing (10)
  • app/build.gradle (1 hunks)
  • app/res/drawable/title_case_tab_vertical.xml (0 hunks)
  • app/res/drawable/title_neutral_tab_vertical.xml (0 hunks)
  • app/res/layout/tabbed_detail_view.xml (1 hunks)
  • app/res/values/styles.xml (1 hunks)
  • app/src/org/commcare/activities/EntityDetailActivity.java (0 hunks)
  • app/src/org/commcare/activities/EntitySelectActivity.java (0 hunks)
  • app/src/org/commcare/adapters/EntityDetailPagerAdapter.java (4 hunks)
  • app/src/org/commcare/fragments/BreadcrumbBarFragment.java (0 hunks)
  • app/src/org/commcare/views/TabbedDetailView.java (4 hunks)
💤 Files with no reviewable changes (5)
  • app/res/drawable/title_case_tab_vertical.xml
  • app/res/drawable/title_neutral_tab_vertical.xml
  • app/src/org/commcare/fragments/BreadcrumbBarFragment.java
  • app/src/org/commcare/activities/EntityDetailActivity.java
  • app/src/org/commcare/activities/EntitySelectActivity.java
🔇 Additional comments (7)
app/build.gradle (1)

463-463: 🛠️ Refactor suggestion

Consider adding ViewPager2 version to dependencies

Since you're migrating to ViewPager2, ensure you've added the appropriate dependency in the dependencies block.

Add the ViewPager2 dependency if not already present:

dependencies {
+    implementation "androidx.viewpager2:viewpager2:1.0.0"
}
app/src/org/commcare/adapters/EntityDetailPagerAdapter.java (3)

24-24: Class extension updated to FragmentStateAdapter appropriately

The class now extends FragmentStateAdapter, which is suitable for use with ViewPager2. This change aligns with the migration from ViewPager to ViewPager2.


41-43: createFragment method correctly overrides and uses @NonNull annotation

The createFragment(int position) method properly replaces getItem(int position), conforms to the FragmentStateAdapter requirements, and uses the @NonNull annotation appropriately.


63-65: getItemCount method correctly implements item count logic

The getItemCount() method replaces getCount() and accurately returns the number of items based on whether the detail is compound.

app/res/layout/tabbed_detail_view.xml (2)

13-27: TabLayout added and configured appropriately

The TabLayout is properly added with appropriate attributes to integrate with ViewPager2. Custom attributes and styles are correctly applied, ensuring consistent tab appearance and behavior.


33-37: ViewPager2 replaces previous ViewPager appropriately

The ViewPager2 is correctly configured with layout_height="0dp" and layout_weight="1" to fill the remaining space. This setup is standard for layouts with dynamic content.

app/res/values/styles.xml (1)

346-348: DetailViewTabStyle added appropriately

The new style DetailViewTabStyle defines android:textColor for tab text, ensuring consistent styling across the application.

app/build.gradle Outdated
@@ -461,7 +460,7 @@ android {
signingConfig signingConfigs.release
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard.cfg'
testProguardFiles 'test-proguard.cfg'

multiDexKeepProguard file('proguard-multidex.pro')
Copy link

Choose a reason for hiding this comment

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

💡 Codebase verification

Multidex configuration needs to be properly set up

The codebase has multiDexKeepProguard configured but is missing essential multidex setup:

  • The proguard-multidex.pro file is missing
  • No multidex dependency is included
  • No explicit multiDexEnabled configuration

To handle method count in release builds, please:

  1. Add the multidex dependency
  2. Enable multidex in defaultConfig
  3. Create the missing proguard-multidex.pro file with proper keep rules
🔗 Analysis chain

Verify multidex configuration for release builds

Since you're adding multidex ProGuard configuration, ensure that multidex is properly enabled for release builds to handle the method count.

Consider adding explicit multidex configuration in your defaultConfig or release buildType if not already present:

android {
    defaultConfig {
+        multiDexEnabled true
    }
}
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for multidex configuration
rg -i "multiDexEnabled" app/build.gradle

Length of output: 40


Script:

#!/bin/bash
# Let's check the entire build.gradle content to understand the configuration better
cat app/build.gradle

# Also check for multidex-related dependencies
rg -i "multidex" app/build.gradle

Length of output: 25477


Script:

#!/bin/bash
# Let's check if there's a proguard-multidex.pro file and its contents
cat proguard-multidex.pro 2>/dev/null || echo "File not found"

# Also check for any multidex-related dependencies
rg -i "implementation.*multidex" app/build.gradle

Length of output: 118


💡 Codebase verification

🛠️ Refactor suggestion

Missing ProGuard configuration file referenced in build.gradle

The build configuration references a non-existent ProGuard configuration file proguard-multidex.pro. This file is required for the multiDexKeepProguard configuration to work properly, and its absence could lead to issues in release builds, especially with ViewPager2 integration.

  • The referenced file proguard-multidex.pro is missing from the project
  • No alternative ProGuard configuration files were found in standard locations
  • ViewPager2-specific ProGuard rules are not defined anywhere in the project
🔗 Analysis chain

Verify the ProGuard configuration for ViewPager2

The addition of multiDexKeepProguard configuration is appropriate for handling method count limitations in release builds. However, ensure that the proguard-multidex.pro file includes the necessary keep rules for ViewPager2 to prevent any ProGuard-related issues.

Consider adding these ViewPager2-specific ProGuard rules to proguard-multidex.pro if not already present:

# Add to proguard-multidex.pro
+-keep class androidx.viewpager2.widget.ViewPager2 { *; }
+-keepclassmembers class androidx.viewpager2.widget.ViewPager2$** { *; }

Let me verify if there are any other ProGuard configuration files in the project that might contain the ViewPager2 rules.


Let me check for any ProGuard configuration files with different extensions and also look for the main ProGuard configuration in the standard Android project locations.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if proguard-multidex.pro exists and contains ViewPager2 rules
if [ -f "proguard-multidex.pro" ]; then
    echo "Checking proguard-multidex.pro for ViewPager2 rules..."
    rg -i "viewpager2" proguard-multidex.pro
else
    echo "proguard-multidex.pro file not found"
fi

Length of output: 117


Script:

#!/bin/bash
# Search for any ProGuard configuration files
echo "Searching for ProGuard configuration files..."
fd -e pro

# Search for ViewPager2 related rules in any text file
echo -e "\nSearching for ViewPager2 ProGuard rules in any file..."
rg -i "viewpager2.*keep" 

Length of output: 264


Script:

#!/bin/bash
# Search for ProGuard configurations with various extensions
echo "Searching for ProGuard configuration files..."
fd -e proguard
fd -e pro
fd -e txt -x grep -l "proguard" {}

# Check standard Android ProGuard locations
echo -e "\nChecking standard ProGuard locations..."
for file in proguard-rules.pro proguard.pro app/proguard-rules.pro app/proguard.pro; do
    if [ -f "$file" ]; then
        echo "Found: $file"
        echo "Contents:"
        cat "$file"
    fi
done

Length of output: 729

}

public int getCurrentTab() {
return mViewPager.getCurrentItem();
}

public int getTabCount() {
return mViewPager.getAdapter().getCount();
return Objects.requireNonNull(mViewPager.getAdapter()).getItemCount();
Copy link

@coderabbitai coderabbitai bot Dec 17, 2024

Choose a reason for hiding this comment

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

⚠️ Potential issue

Handle potential null adapter to avoid NullPointerException

Using Objects.requireNonNull(mViewPager.getAdapter()) can throw a NullPointerException if the adapter is null. Consider checking if the adapter is null and handling it appropriately, or ensure that the adapter is always set before this method is called.

Apply this diff to safely handle the adapter:

 public int getTabCount() {
-    return Objects.requireNonNull(mViewPager.getAdapter()).getItemCount();
+    if (mViewPager.getAdapter() != null) {
+        return mViewPager.getAdapter().getItemCount();
+    } else {
+        return 0;
+    }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return Objects.requireNonNull(mViewPager.getAdapter()).getItemCount();
if (mViewPager.getAdapter() != null) {
return mViewPager.getAdapter().getItemCount();
} else {
return 0;
}

Copy link
Contributor

Choose a reason for hiding this comment

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

@avazirna are we sure mViewPager.getAdapter() can't be null if a caller calls getTabCount out of order on this view ?

Copy link

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Comment on lines +87 to +90
if (detail.isCompound()) {
new TabLayoutMediator(mTabLayout, mViewPager,
(tab, position) -> tab.setText(detail.getDetails()[position].getTitle().getText().evaluate()))
.attach();
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add null checks to prevent potential NullPointerException in TabLayoutMediator

When setting up TabLayoutMediator, ensure that detail.getDetails() and the evaluated titles are not null to prevent a NullPointerException. Consider adding null checks or handling potential null values.

Apply this diff to add null checks:

 if (detail.isCompound()) {
+    if (detail.getDetails() != null && detail.getDetails().length > 0) {
         new TabLayoutMediator(mTabLayout, mViewPager,
             (tab, position) -> {
+                String title = detail.getDetails()[position].getTitle().getText().evaluate();
+                if (title != null) {
                     tab.setText(title);
+                } else {
+                    tab.setText("Tab " + position);
+                }
             })
             .attach();
+    } else {
+        mTabLayout.setVisibility(GONE);
+    }
 } else {
     mTabLayout.setVisibility(GONE);
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (detail.isCompound()) {
new TabLayoutMediator(mTabLayout, mViewPager,
(tab, position) -> tab.setText(detail.getDetails()[position].getTitle().getText().evaluate()))
.attach();
if (detail.isCompound()) {
if (detail.getDetails() != null && detail.getDetails().length > 0) {
new TabLayoutMediator(mTabLayout, mViewPager,
(tab, position) -> {
String title = detail.getDetails()[position].getTitle().getText().evaluate();
if (title != null) {
tab.setText(title);
} else {
tab.setText("Tab " + position);
}
})
.attach();
} else {
mTabLayout.setVisibility(GONE);
}
} else {
mTabLayout.setVisibility(GONE);
}

@avazirna avazirna removed the skip-integration-tests Skip android tests. label Dec 17, 2024
Copy link
Contributor

@shubham1g5 shubham1g5 left a comment

Choose a reason for hiding this comment

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

looks quite good, left some clarifying questions.

Also can we add videos for other places we are using a tabbed view in the app.

Thanks!

app/res/values/dimens.xml Show resolved Hide resolved
}

public int getCurrentTab() {
return mViewPager.getCurrentItem();
}

public int getTabCount() {
return mViewPager.getAdapter().getCount();
return Objects.requireNonNull(mViewPager.getAdapter()).getItemCount();
Copy link
Contributor

Choose a reason for hiding this comment

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

@avazirna are we sure mViewPager.getAdapter() can't be null if a caller calls getTabCount out of order on this view ?

}
mMenu.getChildAt(position).setBackgroundDrawable(getResources().getDrawable(R.drawable.title_case_tab_vertical));
Copy link
Contributor

Choose a reason for hiding this comment

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

why was this removed ?

Copy link
Contributor

Choose a reason for hiding this comment

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

I believe the default style is enough, but will make sure it looks fine everywhere

Copy link
Contributor

Choose a reason for hiding this comment

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

@shubham1g5 Here's the old style to which those drawables where used:
645ebc7e-74a2-11e4-8de9-cf45e08495a6

Copy link
Contributor

Choose a reason for hiding this comment

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

and how does it look now with the changes in PR ?

Copy link
Contributor

Choose a reason for hiding this comment

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

@shubham1g5 my understanding is that this style was used by the old tab system, which was based on the tabbed_detail_menu linear layout. That system was discontinued when PagerTabStrip was added to CC, this is the PR. So this PR doesn't change the style around the tabs, the current and previous styling is what I shared in this comment.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think my question here is more around how does this change not effect Case Detail tabs for example given TabbedDetailView is used there and this line of code seems generic to all TabbedDetailView layouts.

Copy link
Contributor

@avazirna avazirna Dec 20, 2024

Choose a reason for hiding this comment

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

hannn, in the old tab system, we used to add HorizontalMediaView views to tabbed_detail_menu linear layout and apply this style to them, this was removed here. And because we don't add child views to that linear layout anymore, every time we call mMenu.getChildCount() we get 0 and the execution is interrupted. This to say that we haven't been calling this code for quite some time, it's dead code now.

Copy link
Contributor

Choose a reason for hiding this comment

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

got it , thanks for confirming.

@avazirna
Copy link
Contributor

avazirna commented Dec 19, 2024

are we sure mViewPager.getAdapter() can't be null if a caller calls getTabCount out of order on this view ?

@shubham1g5 This logic was not changed as such but I agree that we should null check this, also because ViewPager2.setAdapter() accepts null arguments

@avazirna avazirna requested a review from shubham1g5 December 19, 2024 22:46
Copy link
Contributor

@shubham1g5 shubham1g5 left a comment

Choose a reason for hiding this comment

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

We should add a QA note and make sure it's communicated to QA to test the imapcted screens.

@avazirna avazirna merged commit cb9db27 into commcare_2.55 Dec 20, 2024
2 checks passed
@avazirna avazirna deleted the viewpager2-poc branch December 20, 2024 10:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants