You're on a long train journey - currently in the middle of nowhere. The rolling English hills allows your brain to relax. The intrusive programmer in your head, however, magicks up a solution to something you've been working on for a while. Laptop on. Hotspot on. IDE open. Run application ... Network Error - Failed to connect!
Ever since I was introduced to Mock Service Worker in React, I've made it a goal to ensure that all of my React apps work offline in preparation for the aforementioned inevitability. It allows you to be able to develop your app with a fixed dataset, meaning that it doesn't matter whether you have a network or not, whilst also decoupling the frontend and backend development processes.
Offline mocking also serves another, more important purpose: to ensure that everything can be tested, as one really shouldn't be touching the outside world when performing tests.
When looking how I could replicate this in Flutter, all of the resources I found at the time very much leaned towards solving network mocking for testing purposes only. However, I could not for the life of me find a way of doing this for general development! I wasn't too keen to use yet another package, especially as I already use dio which supports interceptors, so I cooked up a solution.

Problem To Solve

I want to be able to return mocked data, in this case
{"id":1,"name":"Example","attributes":{}}
for all network calls made to example/path within my app.

Store The Response Data Locally

Firstly, I knew I'd need to store the response data somewhere, so I created a fixtures folder in my app. The file didn't have anything fancy in there, just a response I had copied from Chrome's network tab, and made into a Map object.

lib/networking/fixtures/example_fixture.dart

const Map exampleFixture = { "id": 1, "name": "Example", "attributes": {}, };

Create A New Interceptor To Perform The Mocking

Dio has a concept of Interceptors, which (as the name suggests) allow you to intercept all network requests that pass through Dio. I knew that I could utilise this to my advantage, as I had done something similar using the http_mock_adapter when testing. Why didn't I just use this as a solution? Well
http_mock_adapter is a simple to use mocking package for Dio intended to be used in tests. 
I thought it best to just listen to what I was told!

lib/networking/interceptors/mock_interceptor.dart

import 'package:dio/dio.dart'; class MockInterceptor extends Interceptor { MockInterceptor(); static final List<InterceptorMock> _mocks = []; /// Add one or more [InterceptorMock] to the List of available mocks static add(List<InterceptorMock> mocks) { _mocks.addAll(mocks); } static remove(String name) { _mocks.removeWhere((element) => element.name == name); } /// Clear all mocks static empty() { _mocks.clear(); } /// Get all mocks static List<InterceptorMock> getAll() { return _mocks; } /// Get the number of available mocks static int length() { return _mocks.length; } @override void onRequest(RequestOptions options, RequestInterceptorHandler handler) { if (MockInterceptor.length() > 0) { List<InterceptorMock> all = MockInterceptor.getAll(); // all.cast<InterceptorMock?> allows us to use firstWhere with an orElse of null InterceptorMock? mock = all.cast<InterceptorMock?>().firstWhere( (element) => element!.matcher(options), orElse: () => null, ); if (mock != null) { // If we have found a matching mock, resolve the network call with the mock data handler.resolve( Response( requestOptions: options, data: mock.returnData is Function ? mock.returnData() : mock.returnData, statusCode: mock.statusCode, ), ); return; } } super.onRequest(options, handler); } } /// Class to be used in conjunction with [MockInterceptor] Interceptor. /// I.e. MockInterceptor.add([InterceptorMock(...), ...]) class InterceptorMock { const InterceptorMock({ required this.name, required this.matcher, required this.returnData, this.statusCode, }); /// The name of the mock so that it can be matched against other things final String name; /// A function that is passed a [RequestOptions] object which can be used to /// to see if the mock can be applied final bool Function(RequestOptions options) matcher; /// The data to return from the mock. If a function is passed, the return value of /// the function is used final dynamic returnData; /// An optional statusCode to return final int? statusCode; }

Create A Mock

Next, we'll have to actually create a mock. Note, returnData can be anything that you want it to be - you know your application - but if a function is passed, the return data of the function will be sent back to your application.

lib/networking/mocks/example_mock.dart

final exampleMock = InterceptorMock( name: 'example', matcher: (RequestOptions options) => options.path.contains('example/path'), returnData: exampleFixture, );

Add The Interceptor

I opted to do this at the top of my application, meaning that I also wouldn't need to set it up for my tests.

lib/main.dart

void main() async { ... dio.interceptors.addAll([ ... MockInterceptor(), ]); if (env('USE_OFFLINE_MOCKS_ONLY') == '1') { initOfflineMode(); } ... }
MockInterceptor won't actually do anything until mocks are added, meaning that it is safe to add it here. However, I've added an environment variable check to determine whether I want to use the network, or to use my mocks.

initOfflineMode

initOfflineMode() { MockInterceptor.add([ ... exampleMock, ]); }
~
And that's it! I can now develop my app completely offline as I get distracted by the happy sheep and cows flashing past my train window!