Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
6eb07a3
Fix UI stuck on Disconnected during network-change engine restart
pappz Apr 20, 2026
f0df3f5
Bind process to default network and ignore initial callback burst
pappz Apr 20, 2026
b52ce5d
Bump netbird submodule to test branch
pappz Apr 20, 2026
212cf42
Gate network change notifications on engine running
pappz Apr 20, 2026
5a96d64
Merge branch 'main' into fix/reconnection-notification
pappz Apr 20, 2026
bd31953
Update submodule
pappz Apr 20, 2026
b2d0f6d
Silence foreground service notification
pappz Apr 20, 2026
ff71758
Guard default network callback against stale events
pappz Apr 24, 2026
5c6391e
Merge remote-tracking branch 'origin/main' into fix/reconnection-noti…
pappz Apr 24, 2026
48ae2d5
Serialize default network callback state changes
pappz Apr 27, 2026
86b12a0
Warn if default network is a VPN
pappz Apr 27, 2026
2e95a1c
Merge branch 'main' into fix/reconnection-notification
pappz Apr 27, 2026
3bd06f1
Serialize default network callback registration
pappz Apr 27, 2026
c28481f
Suppress old engine state events during restart
pappz Apr 27, 2026
0d8a086
Detect network handover from default-network signal
pappz Apr 27, 2026
061b516
Skip restart when engine reconnects on its own
pappz Apr 27, 2026
878e8b9
Bump netbird submodule to fix/job-stream-state-leak
pappz Apr 27, 2026
380808a
Skip bindProcessToNetwork when default network is a VPN
pappz Apr 27, 2026
4bdbf71
Skip bindProcessToNetwork when network capabilities are unknown
pappz Apr 27, 2026
697fe80
Cancel pending restart on user-driven engine actions
pappz Apr 27, 2026
fb3a440
Remove bindProcessToNetwork from default network callback
pappz Apr 27, 2026
c257fb5
Fix wrapper stacking and stale listener on restart timeout
pappz Apr 28, 2026
d202068
Address CodeRabbit review nits and bump submodule
pappz Apr 29, 2026
8c86028
Merge remote-tracking branch 'origin/main' into fix/reconnection-noti…
pappz Apr 29, 2026
11baba2
Merge branch 'main' into fix/reconnection-notification
pappz Apr 30, 2026
c43d4da
Move peer-list refreshes off the UI thread
pappz Apr 30, 2026
124c8a5
Fix races and listener-suppression leak in EngineRestarter
pappz May 5, 2026
5bbcdf5
Guard PeersFragmentViewModel against teardown race
pappz May 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 26 additions & 8 deletions app/src/main/java/io/netbird/client/ui/home/HomeFragment.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@

import com.airbnb.lottie.LottieAnimationView;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import io.netbird.client.PlatformUtils;
import io.netbird.client.R;
import io.netbird.client.ServiceAccessor;
Expand All @@ -37,6 +40,10 @@ public class HomeFragment extends Fragment implements StateListener {
private ButtonAnimation buttonAnimation;
private boolean isConnected;

// serializes peer-list refreshes off the UI thread; serviceAccessor.getPeersList()
// is a JNI call into Go that can take seconds during engine bootstrap/teardown
private ExecutorService refreshExecutor;

@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
Expand Down Expand Up @@ -115,6 +122,7 @@ public View onCreateView(@NonNull LayoutInflater inflater,
}, 200);
}

refreshExecutor = Executors.newSingleThreadExecutor();
stateListenerRegistry.registerServiceStateListener(this);
return root;
}
Expand All @@ -124,6 +132,10 @@ public void onDestroyView() {
super.onDestroyView();
buttonAnimation.destroy();
stateListenerRegistry.unregisterServiceStateListener(this);
if (refreshExecutor != null) {
refreshExecutor.shutdown();
refreshExecutor = null;
}
FrameLayout openPanelCardView = binding.peersBtn;
openPanelCardView.setOnClickListener(null);
binding = null;
Expand Down Expand Up @@ -191,15 +203,21 @@ public void onDisconnecting() {

@Override
public void onPeersListChanged(long numberOfPeers) {
PeerInfoArray peersList = serviceAccessor.getPeersList();
int connected = 0;
for (int i = 0; i < peersList.size(); i++) {
PeerInfo peer = peersList.get(i);
if(Status.fromLong(peer.getConnStatus()) == Status.CONNECTED) {
connected++;
}
ExecutorService executor = refreshExecutor;
if (executor == null) {
return;
}
updatePeerCount(connected, peersList.size());
executor.execute(() -> {
PeerInfoArray peersList = serviceAccessor.getPeersList();
int connected = 0;
for (int i = 0; i < peersList.size(); i++) {
PeerInfo peer = peersList.get(i);
if(Status.fromLong(peer.getConnStatus()) == Status.CONNECTED) {
connected++;
Comment thread
pappz marked this conversation as resolved.
}
}
updatePeerCount(connected, peersList.size());
});
}

private void updatePeerCount(int connectedPeers, long totalPeers) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.atomic.AtomicBoolean;

import io.netbird.client.PeersStateListener;
import io.netbird.client.PeersStateListenerAdapter;
Expand All @@ -20,6 +24,11 @@ public class PeersFragmentViewModel extends ViewModel implements PeersStateListe
private final PeersStateListenerAdapter peersAdapter;
private final ServiceAccessor serviceAccessor;

// serializes peer-list refreshes off the UI thread; serviceAccessor.getPeersList()
// is a JNI call into Go that can take seconds during engine bootstrap/teardown
private final ExecutorService refreshExecutor = Executors.newSingleThreadExecutor();
private final AtomicBoolean isCleared = new AtomicBoolean(false);

private final MutableLiveData<PeersFragmentUiState> uiState =
new MutableLiveData<>(new PeersFragmentUiState(new ArrayList<>()));

Expand Down Expand Up @@ -70,13 +79,24 @@ public StateListener getStateListener() {

@Override
protected void onCleared() {
isCleared.set(true);
peersAdapter.clearListener();
refreshExecutor.shutdown();
Comment thread
pappz marked this conversation as resolved.
super.onCleared();
}

@Override
public void onPeersChanged(long totalPeers) {
var peers = getPeers(serviceAccessor.getPeersList());
this.uiState.postValue(new PeersFragmentUiState(peers));
if (isCleared.get()) {
return;
}
try {
refreshExecutor.execute(() -> {
var peers = getPeers(serviceAccessor.getPeersList());
uiState.postValue(new PeersFragmentUiState(peers));
});
} catch (RejectedExecutionException ignored) {
// executor shut down concurrently in onCleared; safe to drop
}
}
}
Loading
Loading