flutter singleton

Flutter Singleton (An Ultimate Guide 2023)

Flutter singleton is one of the simplest design patterns. This programming technique saves memory and offers an ideal solution to some situations. Read on to learn more about the singletons: definition, implementation, and examples. 

What is Flutter Singleton?

The Flutter Singleton class has a single instance and offers a global point of access to it. This way, it is easier to control initialization and keep track of the sole instance. Essentially, it limits the instantiation of a class to one object. 

The purpose of using Singleton is to access an object across different parts of our program.

Some classes must have one instance. That is why the operating system should have one file system and one local storage. 

The Singleton creational pattern has stirred controversies since it was introduced in the Gang of Four Book (Design Patterns: Elements of Reusable Object-Orientated Software). Because of the simplicity, the Singletons are often misused. Therefore, you must be careful while implementing this class in your Flutter app.

Related: Using Dart Enum in Flutter

Dart Singleton Implementation

The factory constructor makes the implementation of Singleton in Dart easy and flexible. By making the constructor private, the class can’t be initiated outside the file where it is characterized.  

Here is how the Singleton design pattern is implemented in Dart:

Singleton with Factory Constructor

class SimpleSingleton { static final SimpleSingleton _instance = SimpleSingleton._internal(); factory SimpleSingleton() => _instance; SimpleSingleton._internal(); int a = 0; void inc() { a++; } } main() { final s1 = SimpleSingleton(); final s2 = SimpleSingleton(); print(s1.a); s1.inc(); print(s2.a); }

Dart Singleton with static getter

class SimpleSingleton {
  SimpleSingleton._internal();

  static final SimpleSingleton _instance = SimpleSingleton._internal();
  
  static SimpleSingleton get instance => _instance;

  int a = 0;

  void inc() {
    a++;
  }
}

main() {
  final s1 = SimpleSingleton.instance;
  final s2 = SimpleSingleton.instance;
  print(s1.a);
  s1.inc();
  print(s2.a);
}

Singleton with static fields

class SimpleSingleton {
  SimpleSingleton._internal();

  static final SimpleSingleton instance = SimpleSingleton._internal();

  int a = 0;

  void inc() {
    a++;
  }
}

main() {
  final s1 = SimpleSingleton.instance;
  final s2 = SimpleSingleton.instance;
  print(s1.a);
  s1.inc();
  print(s2.a);
}

Output for all above examples:

0
1

Flutter Singleton Example

Most of the Firebase plugins are implemented as Singletons. This is how the code looks like for FirebaseAuth as a singleton:

class FirebaseSingleton {
  FirebaseSingleton._private();

  static final _instance = FirebaseSingleton._private();

  factory FirebaseSingleton() => _instance;

  final _auth = FirebaseAuth.instance;

  bool get isLoggedIn => _auth.currentUser != null;

  Future<User> signInWithEmailAndPassword(
    String email,
    String password,
  ) async {
    try {
      final user = await _auth.signInWithEmailAndPassword(
        email: email,
        password: password,
      );
      if (user.user == null) {
        throw 'Login failed';
      }
      return user.user!;
    } on FirebaseException catch (e) {
      switch (e.code) {
        case 'invalid-email':
          throw 'Email is invalid';
        case 'user-not-found':
          throw 'User is not found please sign up';
        case 'wrong-password':
          throw 'Email or password is incorrect';
        default:
          throw 'Unable to sign in please try again later';
      }
    } catch (_) {
      throw 'Unable to sign in please try again later';
    }
  }

  Future<User> signUpWithEmailAndPassword({
    required String email,
    required String password,
  }) async {
    try {
      final createdUser = await _auth.createUserWithEmailAndPassword(
        email: email,
        password: password,
      );
      if (createdUser.user == null) {
        throw 'Login failed';
      }
      return createdUser.user!;
    } on FirebaseException catch (e) {
      if (e.code == 'weak-password') {
        throw 'The password provided is too weak.';
      } else if (e.code == 'email-already-in-use') {
        throw 'The account already exists for that email.';
      }
      throw e.message ?? 'Internet connection error';
    } catch (_) {
      rethrow;
    }
  }
}

Creating a Singleton Shared Preference in Flutter

The shared preferences in Flutter help store and retrieve the app-level data that we want to persist even after closing the app. Therefore, creating Share Preference as a Singleton Dart object and using it globally can simplify many tasks. 

Here is how you can simplify SharedPreference as Singleton:

class SharedPrefSingleton {
  static final SharedPrefSingleton _instance = SharedPrefSingleton._internal();

  factory SharedPrefSingleton() => _instance;

  SharedPrefSingleton._internal();

  late SharedPreferences _pref;

  Future<void> initialize() async {
    _pref = await SharedPreferences.getInstance();
  }

  Future<bool> setName(String name) => _pref.setString('name_key', name);

  String get name => _pref.getString('name_key') ?? '';
}

main() async {
  await SharedPrefSingleton().initialize();
  await SharedPrefSingleton().setName('Flutter');
  print(SharedPrefSingleton().name);
}

Build High-Quality Apps with Flutterdesk

Usage of DIO Singleton in Flutter

DIO package is a networking library developed by Flutter. It is a powerful HTTP client for Dart, which supports Interceptors, Global configuration, FormData, Request Cancellation, File downloading, and ConnectionTimeout. 

Here is how you can use the DIO package as Singleton:

class DioSingleton {
  static final DioSingleton _instance = DioSingleton._internal();

  factory DioSingleton() => _instance;

  DioSingleton._internal();

  final _client = Dio(BaseOptions(
    baseUrl: 'https://',
  ));

  Future<void> postData() async {
    try {
      /// POST Request
      await _client.post('path');
    } catch (_) {
      rethrow;
    }
  }
  /// All other requests
}

Flutter Provider Singleton

Singleton providers create a single object. It remembers the first created object and returns it on subsequent calls. Here is the example of Provider Singleton in Flutter:

import 'dart:developer';

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (_) => ProviderSingleton()),
      ],
      child: MaterialApp(
        title: 'SingleTon',
        theme: ThemeData(primarySwatch: Colors.blue),
        home: const MyHomePage(),
      ),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    final providerSingleton = Provider.of<ProviderSingleton>(context);
    return Scaffold(
      appBar: AppBar(
        title: const Text('Singleton'),
        actions: [
          IconButton(
            onPressed: () {
              Navigator.of(context).push(MaterialPageRoute(
                builder: (context) => const SecondPage(),
              ));
            },
            icon: const Icon(Icons.navigate_next_sharp),
          ),
        ],
      ),
      body: Center(
        child: Text(
          'Count - ${providerSingleton.count.toString()}',
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: providerSingleton.increment,
        child: const Icon(Icons.add),
      ),
    );
  }
}

class ProviderSingleton extends ChangeNotifier {
  ProviderSingleton() {
    log('Creating Singleton of ProviderSingleton');
  }

  var _count = 0;

  int get count => _count;

  void increment() {
    _count++;
    notifyListeners();
  }
}

class SecondPage extends StatelessWidget {
  const SecondPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final providerSingleton = Provider.of<ProviderSingleton>(context);
    return Scaffold(
      appBar: AppBar(title: const Text('Second Page')),
      body: Center(
        child: Text(
          'Count Here - ${providerSingleton.count.toString()}',
        ),
      ),
    );
  }
}

Conclusion

This article is aimed to provide a basic understanding of Singletons and how you can use them in Flutter. Hopefully, this blog will provide sufficient information on using singletons with Flutter. still have confusion in using singletons you can hire flutter developers from flutterdesk or can get free consultation.

Umair

Umair

Share on:

Leave a Comment

Your email address will not be published. Required fields are marked *

Translate »