MillerAdulu / cubit_state

A sample to see how state is affected using service locator and cubit
0 stars 0 forks source link

Why use locator(GetIt) with cubit ? #1

Open iampato opened 3 years ago

iampato commented 3 years ago

Totally does not make sense, since locators are just singletons, what if the state change or you want to reset the cubit currentstate?

MillerAdulu commented 3 years ago

@iampato , For Why use locator(GetIt) with cubit I have a preference for constructor dependency injection because it allows me to switch out implementations whenever I want.

As such, most of the cubits I need to call external services to have dependencies. Usually, three is the minimum.

SignInCubit({
    required ApiService apiService,
    required HiveService hiveService,
  }) : super(SignInState.initial()) {
    _apiService = apiService;
    _hiveService = hiveService;
  }

And maybe a database call. My principle is, I never use a class directly, I do a constructor injection. As such, when I want to use the cubit say in multiple places, it would mean I have to keep initializing 3 classes each time.

So a GetIt would allow me to do it only once in one place then reuse it all over.

For what if the state change or you want to reset the cubit current state?

When this happens, GetIt has a handy feature to reset the singleton. In the same manner, I create lazySingleton, I can resetLazySingleton to achieve that effect. So I can this on a widget when I am disposing of it.

Afterthought Had an issue somewhere and I think I can use registerFactory instead for some things. I had totally forgotten about this.

iampato commented 3 years ago

Just create singleton class for your services if an object is in memory it wont create another one, Because behind the single lazySingletons are just dart singleton why should you add a dependency

iampato commented 3 years ago

Example:

import 'package:http/http.dart' as http;
import 'package:sqlite_clean/model/post_model.dart';
import 'package:sqlite_clean/util/http.dart';

class PostsHttpRepo {
  // Setup a singleton
  static final PostsHttpRepo _postsHttpRepo = PostsHttpRepo._internal();
  factory PostsHttpRepo() {
    return _postsHttpRepo;
  }
  PostsHttpRepo._internal();

  HttpClient httpClient = HttpClient();

  // Methods
  Future<Post> getHttpPost() async {
    http.Response response = await httpClient.getRequest(
      "top-headlines?country=us&category=business",
    );
    if (response != null && response.statusCode == 200) {
      print(response.body);
      final post = postFromJson(
        response.body,
      );
      return post;
    } else {
      return null;
    }
  }
}

This is how you register a singleton

// Setup a singleton
  static final PostsHttpRepo _postsHttpRepo = PostsHttpRepo._internal();
  factory PostsHttpRepo() {
    return _postsHttpRepo;
  }
  PostsHttpRepo._internal();

And in my cubits, i just add it like this

class PostCubit extends Cubit<PostState> {
  PostsHttpRepo _postsHttpRepo = PostsHttpRepo(); // as you can see
  PostsDbRepo _postsDbRepo = PostsDbRepo();
  PostCubit() : super(PostInitial());

  Future<void> fetchArticles() async {
    try {
      Post post = await _postsHttpRepo.getHttpPost();
      if (post != null) {
        List<Article> articles = post.articles;
        emit(PostLoaded(
          articles: articles,
          message: "",
        ));
      }
    } on SocketException {
      emit(PostError("is your device online"));
    } catch (e) {
      emit(PostError(e));
    }
  }
}
iampato commented 3 years ago

The same with your lazySingletons without adding a package?

And for registering cubit with Get_it as off flutter_bloc 6 and above as long as you have called BlocProvider up in the tree you can always call `context.read()...(anymethod)

MillerAdulu commented 3 years ago
PostsHttpRepo _postsHttpRepo = PostsHttpRepo(); // as you can see
PostsDbRepo _postsDbRepo = PostsDbRepo();

This is the bit that was my concern. This concern was born during lessons on testing. In Flutter, when running tests, you cannot make network calls in the test environment. As such, if you want to have the same code without having to check whether the app is running in testing, the best route I found in my case was to use constructor injection. Such that, in testing, I can pass in a mock of the API class to return hard-coded dummy data and use the actual implementation in production without adding lines of code in the network implementation to check whether it's in production or testing.

So I can pass in via the constructor PostsHttpRepoMock in testing and PostsHttpRepo in the live environment.

The method you suggest is OK. Only that for a scalable use case, I consider this to be the cleanest implementation.

iampato commented 3 years ago

Mockito My repo layer still remains the same because it takes in dart data types are returned dart data types, in this most of my repos take in maps and return dart models. What I will have to mock is my HTTP class

iampato commented 3 years ago

All of the other layers should run smoothly

MillerAdulu commented 3 years ago

I would be open to seeing a full implementation of your proposition with tests.

iampato commented 3 years ago

There is a project I wrote test lemme copy the same implementation to the above example on post