Guides
Window Focus Refetching
If a user leaves your application and returns and the query data is stale, SwrSharp automatically requests fresh data for you in the background. You can disable this globally or per-query using the refetchOnWindowFocus option.
Basic Usage
Disabling Per-Query
var query = new UseQuery<List<Todo>>(
new QueryOptions<List<Todo>>(
queryKey: new("todos"),
queryFn: async ctx => await FetchTodosAsync(),
refetchOnWindowFocus: false // Disable for this query
),
queryClient
);
Disabling Globally
var queryClient = new QueryClient()
{
DefaultRefetchOnWindowFocus = false // Disable for all queries
};
Important: At the moment SwrSharp's
QueryOptionsconstructor setsrefetchOnWindowFocustotrueby default. That means an individual query will still opt-in to focus refetching unless you explicitly setrefetchOnWindowFocus: falseon theQueryOptionsinstance. TheQueryClient.DefaultRefetchOnWindowFocusfield exists to allow libraries and higher-level code to store a global preference, but code must explicitly opt into using that global value. If you want your queries to inherit the client's global default, set the per-query option accordingly or consider wrappingQueryOptionsconstruction to readQueryClient.DefaultRefetchOnWindowFocus.
How It Works
When refetchOnWindowFocus is true (default):
- User navigates away from your app (window loses focus)
- Data becomes stale while user is away
- User returns to your app (window gains focus)
- SwrSharp automatically refetches stale queries in the background
var query = new UseQuery<Data>(
new QueryOptions<Data>(
queryKey: new("data"),
queryFn: async ctx => await FetchDataAsync(),
staleTime: TimeSpan.FromMinutes(5),
refetchOnWindowFocus: true // Default
),
queryClient
);
// When user returns:
// - If data is < 5 minutes old: No refetch (still fresh)
// - If data is > 5 minutes old: Automatic background refetch
Platform-Specific Implementations
SwrSharp provides an abstraction (IFocusManager) that allows different platforms to implement focus detection their own way.
Default Implementation (Always Focused)
The default DefaultFocusManager assumes the application is always focused. This is suitable for:
- Server-side applications
- Background services
- Non-interactive applications
// Default behavior (always focused)
var queryClient = new QueryClient();
// Uses DefaultFocusManager internally
Blazor WebAssembly
For Blazor WebAssembly, implement focus detection using JavaScript Interop:
public class BlazorFocusManager : IFocusManager
{
private readonly IJSRuntime _jsRuntime;
private bool _isFocused = true;
private DotNetObjectReference<BlazorFocusManager>? _objRef;
public bool IsFocused => _isFocused;
public event Action<bool>? FocusChanged;
public BlazorFocusManager(IJSRuntime jsRuntime)
{
_jsRuntime = jsRuntime;
InitializeAsync();
}
private async void InitializeAsync()
{
_objRef = DotNetObjectReference.Create(this);
// Setup JavaScript focus listener
await _jsRuntime.InvokeVoidAsync(
"window.blazorQueryFocusManager.initialize",
_objRef
);
}
[JSInvokable]
public void OnFocusChanged(bool isFocused)
{
if (_isFocused != isFocused)
{
_isFocused = isFocused;
FocusChanged?.Invoke(isFocused);
}
}
public void SetFocused(bool? isFocused)
{
if (isFocused.HasValue && _isFocused != isFocused.Value)
{
_isFocused = isFocused.Value;
FocusChanged?.Invoke(_isFocused);
}
}
public void SetEventListener(Func<Action<bool>, Action>? setupHandler)
{
// Custom event listener setup
// Not typically needed for Blazor
}
public void Dispose()
{
_objRef?.Dispose();
}
}
JavaScript (wwwroot/js/focusManager.js):
window.blazorQueryFocusManager = {
dotNetRef: null,
initialize: function(dotNetRef) {
this.dotNetRef = dotNetRef;
// Listen to visibility change
document.addEventListener('visibilitychange', () => {
const isFocused = document.visibilityState === 'visible';
dotNetRef.invokeMethodAsync('OnFocusChanged', isFocused);
});
// Also listen to window focus/blur
window.addEventListener('focus', () => {
dotNetRef.invokeMethodAsync('OnFocusChanged', true);
});
window.addEventListener('blur', () => {
dotNetRef.invokeMethodAsync('OnFocusChanged', false);
});
}
};
Usage:
var focusManager = new BlazorFocusManager(jsRuntime);
var queryClient = new QueryClient(focusManager: focusManager);
// Now queries will refetch when window gains focus
WPF
For WPF applications, use the Activated/Deactivated events:
public class WpfFocusManager : IFocusManager
{
private readonly Window _window;
private bool _isFocused;
public bool IsFocused => _isFocused;
public event Action<bool>? FocusChanged;
public WpfFocusManager(Window window)
{
_window = window;
_isFocused = _window.IsActive;
_window.Activated += OnWindowActivated;
_window.Deactivated += OnWindowDeactivated;
}
private void OnWindowActivated(object? sender, EventArgs e)
{
if (!_isFocused)
{
_isFocused = true;
FocusChanged?.Invoke(true);
}
}
private void OnWindowDeactivated(object? sender, EventArgs e)
{
if (_isFocused)
{
_isFocused = false;
FocusChanged?.Invoke(false);
}
}
public void SetFocused(bool? isFocused)
{
if (isFocused.HasValue && _isFocused != isFocused.Value)
{
_isFocused = isFocused.Value;
FocusChanged?.Invoke(_isFocused);
}
}
public void SetEventListener(Func<Action<bool>, Action>? setupHandler)
{
// Custom event listener if needed
}
public void Dispose()
{
_window.Activated -= OnWindowActivated;
_window.Deactivated -= OnWindowDeactivated;
}
}
Usage:
public partial class MainWindow : Window
{
private readonly QueryClient _queryClient;
public MainWindow()
{
InitializeComponent();
var focusManager = new WpfFocusManager(this);
_queryClient = new QueryClient(focusManager: focusManager);
}
}
Avalonia
For Avalonia applications:
public class AvaloniaFocusManager : IFocusManager
{
private readonly Window _window;
private bool _isFocused;
public bool IsFocused => _isFocused;
public event Action<bool>? FocusChanged;
public AvaloniaFocusManager(Window window)
{
_window = window;
_isFocused = window.IsActive;
_window.Activated += OnWindowActivated;
_window.Deactivated += OnWindowDeactivated;
}
private void OnWindowActivated(object? sender, EventArgs e)
{
if (!_isFocused)
{
_isFocused = true;
FocusChanged?.Invoke(true);
}
}
private void OnWindowDeactivated(object? sender, EventArgs e)
{
if (_isFocused)
{
_isFocused = false;
FocusChanged?.Invoke(false);
}
}
public void SetFocused(bool? isFocused)
{
if (isFocused.HasValue && _isFocused != isFocused.Value)
{
_isFocused = isFocused.Value;
FocusChanged?.Invoke(_isFocused);
}
}
public void SetEventListener(Func<Action<bool>, Action>? setupHandler)
{
// Custom event listener if needed
}
public void Dispose()
{
_window.Activated -= OnWindowActivated;
_window.Deactivated -= OnWindowDeactivated;
}
}
MAUI
For .NET MAUI applications:
public class MauiFocusManager : IFocusManager
{
private bool _isFocused = true;
public bool IsFocused => _isFocused;
public event Action<bool>? FocusChanged;
public MauiFocusManager()
{
// Subscribe to application lifecycle events
Application.Current.RequestedThemeChanged += OnAppLifecycleChanged;
}
private void OnAppLifecycleChanged(object? sender, AppThemeChangedEventArgs e)
{
// Use platform-specific APIs
#if ANDROID
var activity = Platform.CurrentActivity;
var isFocused = activity?.HasWindowFocus ?? false;
UpdateFocusState(isFocused);
#elif IOS || MACCATALYST
var isFocused = UIKit.UIApplication.SharedApplication.ApplicationState ==
UIKit.UIApplicationState.Active;
UpdateFocusState(isFocused);
#elif WINDOWS
var window = Application.Current?.Windows?.FirstOrDefault()?.Handler?.PlatformView as
Microsoft.UI.Xaml.Window;
var isFocused = window?.Visible ?? false;
UpdateFocusState(isFocused);
#endif
}
private void UpdateFocusState(bool isFocused)
{
if (_isFocused != isFocused)
{
_isFocused = isFocused;
FocusChanged?.Invoke(isFocused);
}
}
public void SetFocused(bool? isFocused)
{
if (isFocused.HasValue)
UpdateFocusState(isFocused.Value);
}
public void SetEventListener(Func<Action<bool>, Action>? setupHandler)
{
// Custom listener if needed
}
}
Custom Event Listener
In rare circumstances, you may want to manage your own focus events. Use SetEventListener on the QueryClient's FocusManager:
var queryClient = new QueryClient();
queryClient.FocusManager.SetEventListener((handleFocus) => {
// Your custom focus detection logic
// Example: Using a timer to poll focus state
var timer = new System.Timers.Timer(1000);
timer.Elapsed += (s, e) => {
// Check if window is focused (platform-specific)
bool isFocused = CheckIfWindowIsFocused();
handleFocus(isFocused);
};
timer.Start();
// Return cleanup action
return () => {
timer.Stop();
timer.Dispose();
};
});
Managing Focus State Manually
You can manually override the focus state:
var queryClient = new QueryClient();
// Override to unfocused (prevent refetches)
queryClient.FocusManager.SetFocused(false);
// Override to focused (trigger refetches)
queryClient.FocusManager.SetFocused(true);
// Fallback to automatic detection
queryClient.FocusManager.SetFocused(null);
Complete Example
public class TodoApp : IDisposable
{
private readonly QueryClient _queryClient;
private readonly IFocusManager _focusManager;
private UseQuery<List<Todo>>? _todosQuery;
public TodoApp(IFocusManager focusManager)
{
_focusManager = focusManager;
_queryClient = new QueryClient(focusManager: focusManager);
// Monitor focus changes for logging
_focusManager.FocusChanged += OnFocusChanged;
}
public void Initialize()
{
_todosQuery = new UseQuery<List<Todo>>(
new QueryOptions<List<Todo>>(
queryKey: new("todos"),
queryFn: async ctx => await FetchTodosAsync(),
staleTime: TimeSpan.FromMinutes(5),
refetchOnWindowFocus: true // Will refetch when window gains focus
),
_queryClient
);
_todosQuery.OnChange += () =>
{
if (_todosQuery.IsFetchingBackground)
{
// "Refreshing todos in background..."
}
if (_todosQuery.IsSuccess && _todosQuery.Data != null)
{
// "{count} todos loaded"
}
};
_ = _todosQuery.ExecuteAsync();
}
private void OnFocusChanged(bool isFocused)
{
if (isFocused)
{
// "Window gained focus - checking for stale data..."
}
else
{
// "Window lost focus"
}
}
public void Dispose()
{
_focusManager.FocusChanged -= OnFocusChanged;
_todosQuery?.Dispose();
_queryClient.Dispose();
}
}
When to Disable
Consider disabling refetchOnWindowFocus when:
- Data changes infrequently: If your data rarely changes, automatic refetching wastes resources
- User is actively editing: Refetching might disrupt user input or form state
- Expensive queries: Very slow or costly queries shouldn't refetch too often
- Real-time data: If you're using WebSockets or SignalR for real-time updates, you don't need focus refetching
// Disable for static reference data
var countriesQuery = new UseQuery<List<Country>>(
new QueryOptions<List<Country>>(
queryKey: new("countries"),
queryFn: async ctx => await FetchCountriesAsync(),
staleTime: TimeSpan.FromHours(24),
refetchOnWindowFocus: false // Static data, no need to refetch
),
queryClient
);
// Enable for dynamic data
var notificationsQuery = new UseQuery<List<Notification>>(
new QueryOptions<List<Notification>>(
queryKey: new("notifications"),
queryFn: async ctx => await FetchNotificationsAsync(),
staleTime: TimeSpan.FromMinutes(1),
refetchOnWindowFocus: true // Always get latest notifications
),
queryClient
);