flutter
/

GetX Offline Support: Build Resilient Apps That Work Without Internet

Last Sync: Today

On this page

11
0%
5 min read
Remaining
5 minleft

Click any section to jump — progress syncs automatically

flutter

GetX Offline Support: Build Resilient Apps That Work Without Internet

Introduction

Modern apps need to work even when the user is offline or has a poor connection. An offline‑first approach caches data locally, allows actions to be queued, and synchronizes when connectivity returns. GetX, with its reactive state and GetStorage, provides an excellent foundation for building resilient offline experiences. This guide covers how to detect connectivity, cache API responses, queue user actions, and sync them when the app comes back online.

  1. Detecting Network Connectivity

First, you need to know whether the device is online. Use the connectivity_plus package to listen to network changes. Combine it with GetX to make the connectivity state reactive.

YAMLRead-only
1
dependencies:
  connectivity_plus: ^5.0.0
  get: ^4.6.6
  get_storage: ^2.1.1
DARTRead-only
1
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:get/get.dart';

class ConnectivityService extends GetxService {
  final Connectivity _connectivity = Connectivity();
  var isOnline = false.obs;

  @override
  void onInit() {
    super.onInit();
    _initConnectivity();
    _connectivity.onConnectivityChanged.listen(_updateConnectionStatus);
  }

  Future<void> _initConnectivity() async {
    final result = await _connectivity.checkConnectivity();
    _updateConnectionStatus(result);
  }

  void _updateConnectionStatus(ConnectivityResult result) {
    isOnline.value = result != ConnectivityResult.none;
  }
}

Register the service permanently in an initial binding.

DARTRead-only
1
class AppBinding extends Bindings {
  @override
  void dependencies() {
    Get.put(ConnectivityService(), permanent: true);
  }
}

  1. Caching API Responses with GetStorage

Store API responses locally using GetStorage. When the app is offline, read from cache instead of making a network request. You can also cache for a certain time to implement stale‑while‑revalidate strategies.

DARTRead-only
1
class PostRepository {
  final ApiService api;
  final GetStorage cache = GetStorage();
  final Duration cacheDuration = Duration(minutes: 5);

  Future<List<Post>> getPosts() async {
    final cached = cache.read('posts');
    final cachedTime = cache.read('posts_timestamp');
    final isCacheValid = cached != null && cachedTime != null &&
        DateTime.now().difference(DateTime.parse(cachedTime)) < cacheDuration;

    final isOnline = Get.find<ConnectivityService>().isOnline.value;

    if (isOnline) {
      // Fetch fresh data
      final posts = await api.getPosts();
      cache.write('posts', posts.map((p) => p.toJson()).toList());
      cache.write('posts_timestamp', DateTime.now().toIso8601String());
      return posts;
    } else if (isCacheValid) {
      // Return cached data
      final List list = cached;
      return list.map((json) => Post.fromJson(json)).toList();
    } else {
      throw Exception('No internet and no valid cache');
    }
  }
}

  1. Offline‑First Controller with Reactive State

Create a controller that uses the repository and also tracks offline state to show appropriate UI.

DARTRead-only
1
class PostsController extends GetxController with StateMixin<List<Post>> {
  final PostRepository repository;
  final ConnectivityService connectivity = Get.find();

  PostsController(this.repository);

  @override
  void onInit() {
    super.onInit();
    loadPosts();
    // Reload when connectivity returns
    ever(connectivity.isOnline, (online) {
      if (online) loadPosts();
    });
  }

  Future<void> loadPosts() async {
    change(null, status: RxStatus.loading());
    try {
      final posts = await repository.getPosts();
      change(posts, status: RxStatus.success());
    } catch (e) {
      change(null, status: RxStatus.error(e.toString()));
    }
  }
}

  1. Queuing Actions for When Offline

For operations like creating a post, you can queue them locally when offline and sync them when connectivity returns. Store actions in a list and process them one by one.

DARTRead-only
1
class OfflineQueueService extends GetxService {
  final GetStorage _storage = GetStorage();
  var pendingActions = <Map<String, dynamic>>[].obs;

  @override
  void onInit() {
    super.onInit();
    // Load pending actions from storage
    final saved = _storage.read('pending_actions');
    if (saved != null) pendingActions.assignAll(saved.cast<Map<String, dynamic>>());
    // Listen for online status to sync
    final connectivity = Get.find<ConnectivityService>();
    ever(connectivity.isOnline, (online) {
      if (online) syncAll();
    });
  }

  void addAction(String type, Map<String, dynamic> data) {
    final action = {'type': type, 'data': data, 'timestamp': DateTime.now().toIso8601String()};
    pendingActions.add(action);
    _storage.write('pending_actions', pendingActions.toList());
  }

  Future<void> syncAll() async {
    if (pendingActions.isEmpty) return;
    // Make a copy to avoid modification during iteration
    final actions = List<Map<String, dynamic>>.from(pendingActions);
    for (final action in actions) {
      try {
        await _executeAction(action);
        pendingActions.remove(action);
        _storage.write('pending_actions', pendingActions.toList());
      } catch (e) {
        // Log error, maybe keep action for later
      }
    }
  }

  Future<void> _executeAction(Map<String, dynamic> action) async {
    final type = action['type'];
    final data = action['data'];
    if (type == 'create_post') {
      await api.createPost(data);
    }
    // other action types
  }
}

  1. Showing Offline UI

Use the connectivity service to show an offline banner or change the UI. For example, show a snackbar when offline and hide it when back online.

DARTRead-only
1
class OfflineBanner extends StatelessWidget {
  final connectivity = Get.find<ConnectivityService>();

  @override
  Widget build(BuildContext context) {
    return Obx(() {
      if (!connectivity.isOnline.value) {
        return Container(
          color: Colors.orange,
          padding: EdgeInsets.all(8),
          child: Row(
            children: [
              Icon(Icons.wifi_off, color: Colors.white),
              SizedBox(width: 8),
              Text('You are offline', style: TextStyle(color: Colors.white)),
            ],
          ),
        );
      }
      return SizedBox.shrink();
    });
  }
}

  1. Complete Offline Post Creation Example

Putting it all together, here's how a controller can use both caching and queuing.

DARTRead-only
1
class PostController extends GetxController {
  final PostRepository repository;
  final OfflineQueueService queue;
  final ConnectivityService connectivity;
  var posts = <Post>[].obs;
  var isCreating = false.obs;

  PostController(this.repository, this.queue, this.connectivity);

  @override
  void onInit() {
    super.onInit();
    loadPosts();
    ever(connectivity.isOnline, (_) => loadPosts());
  }

  Future<void> loadPosts() async {
    final data = await repository.getPosts();
    posts.assignAll(data);
  }

  Future<void> createPost(String title, String body) async {
    isCreating.value = true;
    try {
      if (connectivity.isOnline.value) {
        // online: send immediately
        final newPost = await api.createPost(title, body);
        posts.insert(0, newPost);
      } else {
        // offline: queue action
        queue.addAction('create_post', {'title': title, 'body': body});
        Get.snackbar('Offline', 'Post saved locally, will sync when online');
      }
    } finally {
      isCreating.value = false;
    }
  }
}

Best Practices

  • Use connectivity_plus – It’s the standard package for network detection.
  • Cache with expiration – Set a TTL to serve stale data while fetching fresh.
  • Queue non‑idempotent actions – Only queue actions that can be safely retried (e.g., POST).
  • Show offline indicators – Keep the user informed about connectivity status.
  • Sync in the background – Use a worker that listens to connectivity changes to process the queue.
  • Handle conflicts – When syncing, resolve conflicts (e.g., timestamp, server‑generated IDs).
  • Persist queues – Use GetStorage to store pending actions so they survive app restarts.

Common Mistakes

  • ❌ Not handling race conditions – Multiple sync attempts may send duplicates. ✅ Use a mutex flag or process queue one at a time.
  • ❌ Assuming cache is always valid – Cache may be stale; always validate.
  • ❌ Not clearing cache when logging out – User data may persist incorrectly. ✅ Clear relevant cache on logout.
  • ❌ Storing large objects without expiration – Accumulates unnecessary data. ✅ Use TTL and clean up old entries.

FAQ

  • Q: Can I use GetStorage for caching large lists?
    A: Yes, but it’s a simple key‑value store. For large amounts of data, consider Hive or SQLite.
  • Q: How do I detect a weak connection?
    A: connectivity_plus only tells you if any network is available, not its quality. You can implement a ping test or use dio with timeouts.
  • Q: How to sync offline changes when the app is in the background?
    A: Use a background fetch service or listen to connectivity changes in a service; you can also use a timer to retry periodically.
  • Q: What about offline support for Firebase?
    A: Firebase has built‑in offline persistence; you can combine it with GetX for state management. The concepts are similar but use Firebase's offline capabilities.
  • Q: How to test offline mode?
    A: Use a simulator or real device and enable/disable Wi‑Fi. You can also mock the connectivity service in tests to simulate offline/online transitions.

Conclusion

Building offline‑capable apps with GetX is achievable by combining connectivity detection, caching, and action queues. With reactive state, you can provide a seamless experience even when the network is unavailable. The patterns shown here give you a solid foundation for an offline‑first architecture.

Test Your Knowledge

Q1
of 3

Which package is commonly used to detect network connectivity in Flutter?

A
network_info_plus
B
connectivity_plus
C
internet_connection_checker
D
wifi_info_flutter
Q2
of 3

How can you persist pending actions to survive app restarts?

A
Store them in memory
B
Use GetStorage or SharedPreferences
C
Use a local database
D
Both B and C
Q3
of 3

What is the purpose of setting a cache expiration (TTL)?

A
To reduce storage size
B
To ensure data is not too old when the app is offline
C
To improve performance
D
To prevent memory leaks

Previous

getx dynamic ui rendering

Next

getx memory management

Related Content

Need help?

Explore our comprehensive docs or start a chat with our tech experts.