Skip to content

Conversation

@pappz
Copy link
Collaborator

@pappz pappz commented Nov 27, 2025

VPNServiceRepository created its own service binding which caused conflicts when unbinding. It would disconnect other components relying on the service.

Now NetworksFragmentViewModel uses ServiceAccessor from MainActivity, which maintains a single shared service binding across all components.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added public route management APIs to enable selective route activation and deactivation.
    • Introduced route change listener capabilities for monitoring real-time route updates.
  • Refactor

    • Streamlined service access architecture to improve maintainability and reduce public API surface.

✏️ Tip: You can customize this high-level summary in your review settings.

VPNServiceRepository created its own service binding which caused conflicts
when unbinding - it would disconnect other components relying on the service.

Now NetworksFragmentViewModel uses ServiceAccessor from MainActivity, which
maintains a single shared service binding across all components.
@coderabbitai
Copy link

coderabbitai bot commented Nov 27, 2025

Walkthrough

This pull request refactors the VPN service access pattern by removing VPNServiceRepository and VPNServiceBindListener, replacing them with a ServiceAccessor interface. Route management and listener capabilities are now exposed through MainActivity and integrated into NetworksFragmentViewModel, while data transformation logic migrates from the repository to the ViewModel.

Changes

Cohort / File(s) Summary
Repository Layer Removal
app/src/main/java/io/netbird/client/repository/VPNServiceRepository.java, app/src/main/java/io/netbird/client/repository/VPNServiceBindListener.java, app/src/main/java/io/netbird/client/MyApplication.java
Removed entire VPNServiceRepository class and VPNServiceBindListener interface. Eliminated public accessor method getVPNServiceRepository() from MyApplication and cleaned up related imports (IntentFilter, LocalBroadcastManager, NetworkChangeNotifier).
Service Access Contract
app/src/main/java/io/netbird/client/ServiceAccessor.java
Added four new public interface methods: selectRoute(String), deselectRoute(String), addRouteChangeListener(RouteChangeListener), removeRouteChangeListener(RouteChangeListener).
Activity-Level Route Management
app/src/main/java/io/netbird/client/MainActivity.java
Added four new public methods exposing route management: selectRoute(String), deselectRoute(String), addRouteChangeListener(RouteChangeListener), removeRouteChangeListener(RouteChangeListener), each delegating to mBinder with null-checks. Replaced NetworkChangeNotifier with RouteChangeListener import.
ViewModel Refactoring
app/src/main/java/io/netbird/client/ui/home/NetworksFragmentViewModel.java
Replaced VPNServiceRepository and VPNServiceBindListener with ServiceAccessor dependency. Removed VPNServiceBindListener from class interface. Added public static factory method getFactory(ServiceAccessor). Moved data transformation logic (createPeerRoutesList, createNetworkDomainsList, getNetworks, getRoutingPeers) into ViewModel. Updated route operations and lifecycle listener registration to use ServiceAccessor.
Fragment Dependency Wiring
app/src/main/java/io/netbird/client/ui/home/NetworksFragment.java
Added ServiceAccessor field and runtime validation in onAttach(). Updated ViewModel factory instantiation from ViewModelProvider.Factory.from() to NetworksFragmentViewModel.getFactory(serviceAccessor).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Areas requiring extra attention:

  • Data transformation logic now embedded in NetworksFragmentViewModel (createPeerRoutesList, createNetworkDomainsList, getNetworks, getRoutingPeers) — verify error handling and IP resolution logic are correct
  • Verify ServiceAccessor contract completeness and null-safety in all MainActivity delegation methods
  • Lifecycle management changes: confirm route change listener is properly registered/unregistered without memory leaks
  • Exception propagation from selectRoute/deselectRoute through the delegation chain
  • Fragment dependency injection pattern validation in NetworksFragment.onAttach()

Poem

🐰 A rabbit hops through code with glee,
Old repositories fade to history,
ServiceAccessor brings structure clean,
Route management queues between,
Simpler paths now clearly seen! ✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Replace VPNServiceRepository with ServiceAccessor pattern' directly and clearly describes the main architectural change throughout the pull request, accurately reflecting the core objective.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix-unbind

Tip

📝 Customizable high-level summaries are now available in beta!

You can now customize how CodeRabbit generates the high-level summary in your pull requests — including its content, structure, tone, and formatting.

  • Provide your own instructions using the high_level_summary_instructions setting.
  • Format the summary however you like (bullet lists, tables, multi-section layouts, contributor stats, etc.).
  • Use high_level_summary_in_walkthrough to move the summary from the description to the walkthrough section.

Example instruction:

"Divide the high-level summary into five sections:

  1. 📝 Description — Summarize the main change in 50–60 words, explaining what was done.
  2. 📓 References — List relevant issues, discussions, documentation, or related PRs.
  3. 📦 Dependencies & Requirements — Mention any new/updated dependencies, environment variable changes, or configuration updates.
  4. 📊 Contributor Summary — Include a Markdown table showing contributions:
    | Contributor | Lines Added | Lines Removed | Files Changed |
  5. ✔️ Additional Notes — Add any extra reviewer context.
    Keep each section concise (under 200 words) and use bullet or numbered lists for clarity."

Note: This feature is currently in beta for Pro-tier users, and pricing will be announced later.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

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: 2

🧹 Nitpick comments (2)
app/src/main/java/io/netbird/client/ServiceAccessor.java (1)

15-16: Consider using a more specific exception type.

The methods throw a generic Exception, which forces callers to handle all exception types. If the underlying mBinder methods throw specific exceptions, consider propagating those types or creating a custom exception class for route operations.

app/src/main/java/io/netbird/client/ui/home/NetworksFragmentViewModel.java (1)

54-60: Consider graceful error handling instead of RuntimeException.

Wrapping exceptions in RuntimeException will crash the app if the underlying gomobile library throws. Consider logging the error and returning an empty list, or propagating errors through a UI-visible error state.

         try {
             for (int i = 0; i < peerRoutes.size(); i++) {
                 routes.add(peerRoutes.get(i));
             }
         } catch (Exception e) {
-            throw new RuntimeException(e);
+            Log.e("NetworksFragmentVM", "Failed to get peer routes", e);
+            // Return partial results collected so far
         }

         return routes;

Also applies to: 86-88

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 267ace7 and e98a704.

📒 Files selected for processing (7)
  • app/src/main/java/io/netbird/client/MainActivity.java (2 hunks)
  • app/src/main/java/io/netbird/client/MyApplication.java (0 hunks)
  • app/src/main/java/io/netbird/client/ServiceAccessor.java (2 hunks)
  • app/src/main/java/io/netbird/client/repository/VPNServiceBindListener.java (0 hunks)
  • app/src/main/java/io/netbird/client/repository/VPNServiceRepository.java (0 hunks)
  • app/src/main/java/io/netbird/client/ui/home/NetworksFragment.java (4 hunks)
  • app/src/main/java/io/netbird/client/ui/home/NetworksFragmentViewModel.java (2 hunks)
💤 Files with no reviewable changes (3)
  • app/src/main/java/io/netbird/client/repository/VPNServiceBindListener.java
  • app/src/main/java/io/netbird/client/MyApplication.java
  • app/src/main/java/io/netbird/client/repository/VPNServiceRepository.java
🧰 Additional context used
🧬 Code graph analysis (1)
app/src/main/java/io/netbird/client/ui/home/NetworksFragment.java (1)
app/src/main/java/io/netbird/client/ui/home/NetworksFragmentViewModel.java (1)
  • NetworksFragmentViewModel (18-190)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build-debug
  • GitHub Check: Analyze (java-kotlin)
🔇 Additional comments (7)
app/src/main/java/io/netbird/client/ui/home/NetworksFragment.java (2)

41-56: LGTM - Consistent dependency injection pattern.

The ServiceAccessor check follows the same pattern as the existing StateListenerRegistry check, ensuring the hosting activity implements the required interface. Since onAttach is called before onViewCreated, serviceAccessor will be properly initialized when used.


69-70: LGTM - Factory-based ViewModel instantiation.

Using NetworksFragmentViewModel.getFactory(serviceAccessor) properly injects the dependency while adhering to the ViewModel lifecycle.

app/src/main/java/io/netbird/client/ServiceAccessor.java (1)

18-19: LGTM - Standard listener registration pattern.

The add/remove listener methods follow the standard observer pattern for route change notifications.

app/src/main/java/io/netbird/client/ui/home/NetworksFragmentViewModel.java (4)

28-39: LGTM - Standard ViewModel factory pattern.

The factory correctly creates the ViewModel with the injected ServiceAccessor dependency. The unchecked cast warning is unavoidable with ViewModelProvider's generic API.


41-45: LGTM - Proper cleanup in onCleared.

The ViewModel correctly unregisters itself as a listener when cleared, preventing memory leaks and dangling callbacks.


128-134: LGTM - Correct use of postValue for background thread updates.

Using postValue instead of setValue is appropriate since this method may be called from background threads via onRouteChanged and onPeersListChanged callbacks.


23-26: I'll help you verify the timing of listener registration in the NetworksFragmentViewModel constructor. Let me investigate the service binding lifecycle and how it relates to ViewModel creation.
<function_calls>

#!/bin/bash

Find MainActivity and check service binding lifecycle

fd -t f "MainActivity.java"


</function_calls>
<function_calls>

#!/bin/bash

Find ServiceAccessor to check listener registration logic

fd -t f "ServiceAccessor.java"


</function_calls>
<function_calls>

#!/bin/bash

Search for onServiceConnected and service binding patterns

rg -n "onServiceConnected|onServiceDisconnected|mBinder" --type java -B2 -A5


</function_calls>

Comment on lines +369 to +377
@Override
public void selectRoute(String route) throws Exception {
if (mBinder == null) {
Log.w(LOGTAG, "VPN binder is null");
return;
}

mBinder.selectRoute(route);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Silent failure on null binder may cause unexpected behavior.

When mBinder is null, the method returns silently without throwing or indicating failure. Callers expect either success or an exception (per the method signature). Consider throwing an exception or returning a status to indicate the operation couldn't be performed.

     @Override
     public void selectRoute(String route) throws Exception {
         if (mBinder == null) {
             Log.w(LOGTAG, "VPN binder is null");
-            return;
+            throw new IllegalStateException("VPN service not bound");
         }

         mBinder.selectRoute(route);
     }
🤖 Prompt for AI Agents
In app/src/main/java/io/netbird/client/MainActivity.java around lines 369 to
377, the method selectRoute currently returns silently when mBinder is null even
though its signature declares it throws Exception; change the null-handling to
log the condition and then throw a suitable exception (e.g.,
IllegalStateException or a checked Exception consistent with the signature) so
callers receive an error instead of silent failure; keep the existing log
warning, then throw the exception with a clear message like "VPN binder is null"
to signal the operation couldn't be performed.

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.

3 participants