How to mock a templated class in Flutter? - flutter

I am currently developing an application that uses MVVM architecture, but I don't think this is relevant to my problem. When I was adding tests to my View Model I ran into a problem, when I was trying to mock my View Model, since it was being passed to the Screen State class only the type of the View Model via template, I couldn't find a way to mock it.
Something like this:
class MenuScreen extends Screen {
const MenuScreen({Key? key}) : super(key: key);
#override
_MenuScreenState createState() => _MenuScreenState();
}
_MenuScreenState class extends ScreenState<MenuScreen,
MenuViewModel, MenuData> {
...
}
abstract class ScreenState<T extends Screen, T2 extends ViewModel<T3>,
T3 extends StateData> extends State<T> {
ScreenState();
late final T2 viewModel;
Widget buildScreen(BuildContext context);
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ValueListenableProvider<T3>.value(value: viewModel),
],
builder: (context, _) => buildScreen(context),
);
}
#override
#mustCallSuper
void initState() {
super.initState();
viewModel = context.read<ViewModelFactory>().create<T2>();
}
#override
#mustCallSuper
void dispose() {
viewModel.dispose();
super.dispose();
}
}
ViewModelFactory is responsible for create an instance of MenuViewModel, that for my unit tests scenario should be something like MockMenuViewModel.
Is there any way I can mock up MenuViewModel within this ScreenState class or somehow inject the mock view model instance i want in the screen state instance?

Related

Accessing state in widget and making class immutable

I need to expose a couple of functions of a Stateful Widget. Since these functions depend on the state of the widget, I created a variable to store the state.
However, I am getting a compile time warning:
This class (or a class that this class inherits from) is marked as '#immutable', but one or more of its instance fields aren't final.
My Code:
class ItemWidget extends StatefulWidget {
final Record record;
final Function additem;
final Function removeItem;
var state;
ItemWidget(this.record, this.additem, this.removeItem);
#override
_ItemWidgetState createState() {
return this.state = new _ItemWidgetState();
}
// These are public functions which I need to expose.
bool isValid() => state.validate();
void validate() => state.validate();
}
Is there a better way /correct way of achieving this?
Thanks.
You should write the function on state, and access it via GlobalKey.
import 'package:flutter/material.dart';
class ItemWidget extends StatefulWidget {
final Record record;
final Function additem;
final Function removeItem;
const ItemWidget(
Key? key,
this.record,
this.additem,
this.removeItem,
) : super(key: key);
#override
ItemWidgetState createState() => ItemWidgetState();
}
class ItemWidgetState extends State<ItemWidget> {
bool isValid() {
return true;
}
void validate() {}
#override
Widget build(BuildContext context) {
// TODO: implement build
throw UnimplementedError();
}
}
https://api.flutter.dev/flutter/widgets/GlobalKey-class.html

Access state from ui without an if statement

am following this Bloc's official example and I couldn't find a way how to access the state without that if statement.
Let's have the example below, I would like to display a specific text based on the initial value of showText, the only possible solution to access the state is via:
if(statement is ExampleInitial) {state.showText? return Text("yes") : return Text("no")}
But am finding this solution hard to implement when you have more values with initial values. Or am I doing this wrong?
////////// bloc
class ExampleBloc extends Bloc<ExampleEvent, ExampleState> {
ExampleBloc() : super(const ExampleInitial()) {
on<ExampleStarted>(_onExampleStarted);
}
void _onExampleStarted(ExampleStarted event, Emitter<ExampleState> emit) {
emit(const ExampleInitial());
}
}
////////// event
part of 'example_bloc.dart';
abstract class ExampleEvent extends Equatable {
const ExampleEvent();
}
class ExampleStarted extends ExampleEvent {
#override
List<Object> get props => [];
}
////////// state
part of 'example_bloc.dart';
abstract class ExampleState extends Equatable {
const ExampleState();
}
////////// state
class ExampleInitial extends ExampleState {
final bool showText = false;
const ExampleInitial();
#override
List<Object> get props => [showText];
}
// ui
class CreateExampleScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocBuilder<ExampleBloc, ExampleState>(
builder: (context, state) {
return state.showText ? Text("yes") :Text("no"); // can't access to state.showText
});
}
}
You can declare a variable inside Bloc Class which will be global and need to be set inside the 'bloc.dart' file like in the case of Provider Package. This variable does not need state to be checked before accessing it in UI. You can access this value from the Navigation tree using context.
////////// bloc
class ExampleBloc extends Bloc<ExampleEvent, ExampleState> {
ExampleBloc() : super(const ExampleInitial()) {
on<ExampleStarted>(_onExampleStarted);
}
bool showText = false;
void _onExampleStarted(ExampleStarted event, Emitter<ExampleState> emit) {
emit(const ExampleInitial());
}
}
// ui
class CreateExampleScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocProvider.of<ExampleBloc>(context).showText
? const Text('Yes')
: const Text('No');
}
}
There is another way in which you declare abstract State Class to always have the boolean value. So, whatever new class extends those State will have inherited boolean value from parent class. This concept is called inheritance in OOP.
////////// state
abstract class ExampleState extends Equatable {
const ExampleState();
final bool showText = false;
}
////////// state
class ExampleInitial extends ExampleState {
const ExampleInitial();
// You can also set ExampleInitial to accept showText and send it to its
// parent class using 'super' method in constructor,
// if parent class has constructor with 'showText' as boolean
#override
List<Object> get props => [];
}
// ui
class CreateExampleScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocBuilder<ExampleBloc, ExampleState>(builder: (context, state) {
return state.showText ? const Text("yes") : const Text("no");
});
}
}
A pragmatic usecase for different State Classes having different state variables is as follows:
Let's account for three states while fetching data from api
-if(state is DataLoadingState),
// there is no need for state
-if(state is DataLoadedState)
// state need to have a variable named weatherData containing temperatures, cities and so on.
-if(state is ErrorWhileLoadingState)
// state needs to have a reason for the error. For example: errorMsg: 'Internal Server Error'
So, you need to check the state before accessing its values.

How to use ever() with Flutter getx variable in GetxController if I didn't add .obs

I am using Flutter getx package.
I know how to use "ever" with (.obs) variable like this
class CountController extends GetxController {
final count = 0.obs;
#override
void onInit() {
ever(count1, (_) => print("$_ has been changed"));
super.onInit();
}
}
But how to use "ever()" or triggering specific callbacks when variable change if the variable doesn't have (.obs) because I am using GetBuilder (not : Obx or GetX) in my view
class AnyScreen extends StatelessWidget {
final controller = Get.put(CounterController());
#override
Widget build(BuildContext context) {
ever(controller.counter, (value) => print("$value has been changed"));
}
#override
Widget build(BuildContext context) {
return Text("whatever!");
}
}
NOTE: Workers should always be used when starting a Controller or Class, so it should always be on onInit (recommended), Class constructor, or the initState of a StatefulWidget (this practice is not recommended in most cases, but it shouldn't have any side effects)
docs

How to use the same BLoC for different widgets in Flutter?

I have 2 custom widgets, and I want to use the same Bloc file.
My Bloc file gets data from the internet in the constructor.
class MyBloc {
// StreamControllers, StreamSinks, Streams ...
MyBloc() {
getDataFromInternet();
}
}
class MyWidget1 extends StatefulWidget {
MyWidget1({Key key}) : super(key: key);
#override
_MyWidget1State createState() => _MyWidget1State();
}
class _MyWidget1State extends State<MyWidget1> {
MyBloc _bloc;
#override
void initState() {
_bloc = MyBloc();
super.initState();
}
}
class MyWidget2 extends StatefulWidget {
MyWidget2({Key key}) : super(key: key);
#override
_MyWidget2State createState() => _MyWidget2State();
}
class _MyWidget2State extends State<MyWidget2> {
MyBloc _bloc;
#override
void initState() {
_bloc = MyBloc();
super.initState();
}
}
My problem is, that it downloads the data every time the screen changes (any of the two widgets appear on the screen).
Should I pass the initialized bloc object to the widgets in the constructors, and not create a new Bloc in the widgets constructor? I don't want to save the data and write logic to check if I already downloaded it or not.
Use this bloc implementation https://bloclibrary.dev/
Your bloc will have single instance with it's single state at the moment. Invoke new state depending on previous and you will never has problems with unneeded queries or something like this.

flutter_bloc - how can i get value without BlocBuilder?

I'm still a beginner with streams and bloc pattern.
I would like to do following:
Trigger an event.
Based on the event get back a state with an object
Store this object as JSON in a database.
All examples are showing, how an object can be displayed in a widget with BlocBuilder. But I don't need to display the value, only get it and store it. I can't figure out how to get the value into a variable.
How can I do that? In the View class I'm dispatching the event, but now I need to know how to get the object in the state back without using BlocBuilder.
Here are the details:
Bloc
class SchoolBloc extends Bloc<SchoolEvent, SchoolState> {
final SchoolRepository _schoolRepository;
StreamSubscription _schoolSubscription;
SchoolBloc({#required SchoolRepository schoolRepository})
: assert(schoolRepository != null),
_schoolRepository = schoolRepository;
#override
SchoolState get initialState => SchoolsLoading();
#override
Stream<SchoolState> mapEventToState(SchoolEvent event) async* {
if (event is LoadSchool) {
yield* _mapLoadSchoolToState();
Stream<SchoolState> _mapLoadSchoolToState(LoadSchool event) async* {
_schoolSubscription?.cancel();
_schoolSubscription = _schoolRepository.school(event.id).listen(
(school) {
SchoolLoaded(school);
}
);
}
Event
#immutable
abstract class SchoolEvent extends Equatable {
SchoolEvent([List props = const []]) : super(props);
}
class LoadSchool extends SchoolEvent {
final String id;
LoadSchool(this.id) : super([id]);
#override
String toString() => 'LoadSchool';
}
State
#immutable
abstract class SchoolState extends Equatable {
SchoolState([List props = const []]) : super(props);
}
class SchoolLoaded extends SchoolState {
final School school;
SchoolLoaded([this.school]) : super([school]);
#override
String toString() => 'SchoolLoaded { school: $school}';
}
View
class CourseView extends StatefulWidget {
#override
State<StatefulWidget> createState() => _CourseViewState();
}
class _CourseViewState extends State<CourseView> {
#override
initState() {
super.initState();
print("this is my init text");
final _schoolBloc = BlocProvider.of<SchoolBloc>(context);
_schoolBloc.dispatch(LoadSchool("3kRHuyk20UggHwm4wrUI"));
// Here I want to get back the school object and save it to a db
}
Test that fails
For testing purposes I have done following:
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:teach_mob/core/blocs/school/school.dart';
class CourseView extends StatefulWidget {
#override
State<StatefulWidget> createState() => _CourseViewState();
}
class _CourseViewState extends State<CourseView> {
#override
void initState() {
super.initState();
BlocProvider.of<SchoolBloc>(context)
.dispatch(LoadSchool("3kRHuyk20UggHwm4wrUI"));
}
#override
Widget build(BuildContext context) {
return BlocListener<SchoolBloc, SchoolState>(
listener: (context, state) {
print("BlocListener is triggered");
},
child: Text("This is a test")
);
}
}
The LoadSchool event is triggered. The text in the child attribute of BlocListener is displayed, but the listener function that should print "BlocListener is triggered" is not executed.
Use BlocListener. It is meant to be used for those cases you mention.