I'm using the Bloc state mangement Library -
currently the version of the Library is 8.1.1
I've followed some guides and tutorials online , but I encountered an error regarding the reference to "this" in AddItem event functions below:
import 'dart:async';
import 'package:foodapptest/models/basket_model.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../models.dart';
import 'package:foodapptest/ResturantMenuItem.dart';
part 'basket_event.dart';
part 'basket_state.dart';
class BasketBloc extends Bloc<BasketEvent,BasketState>
//TO do fix bloc
{
BasketBloc() : super(BasketLoading()) {
on<BasketStart>(_onStartBasket);
on<ItemAdd>(_onAddItem);
}
}
Future<void> _onStartBasket(
BasketStart event,
Emitter<BasketState> emit,
) async {
emit( BasketLoading());
try{
emit(BasketLoaded(basket: Basket(),),);
}catch (e) {
print(e);
}
}
Future<void> _onAddItem(
ItemAdd event,
Emitter<BasketState> emit,
) async {
final state = this.state;
if(state is BasketLoaded) {
try {
emit(BasketLoaded(basket: state.basket.copyWith(items: List.from(state.basket.items)..add(event.item))));
} catch (_) {}
}
}
}
The error 1:
The error 2:
all helps appreciated :)
Thank you!
Problem solved: The functions _onAddItem.. should be inside the class.
import 'dart:async';
import 'package:foodapptest/models/basket_model.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../models.dart';
import 'package:foodapptest/ResturantMenuItem.dart';
part 'basket_event.dart';
part 'basket_state.dart';
class BasketBloc extends Bloc<BasketEvent,BasketState>
//TO do fix bloc
{
BasketBloc() : super(BasketLoading()) {
on<BasketStart>(_onStartBasket);
on<ItemAdd>(_onAddItem);
}
Future<void> _onStartBasket(
BasketStart event,
Emitter<BasketState> emit,
) async {
emit( BasketLoading());
try{
emit(BasketLoaded(basket: Basket(),),);
}catch (e) {
print(e);
}
}
Future<void> _onAddItem(
ItemAdd event,
Emitter<BasketState> emit,
) async {
final state = this.state;
if(state is BasketLoaded) {
try {
emit(BasketLoaded(basket: state.basket.copyWith(items: List.from(state.basket.items)..ad
d(event.item))));
} catch (_) {}
}
}
}
}
Related
Problem
I'm developing a simple Todo app and trying to create test for my files before I develop the UI.
I created a repository which exposes a Stream<List<Todo>>> and I'm listening to it in the TodosOverviewStore. I tried to test if the Store.value would update when the repository stream emitted a new value, but I can't get it right.
Code
todo_repository_impl.dart
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../domain/entities/todo.dart';
import '../domain/repositories/todo_repository.dart';
class TodoRepositoryImpl implements TodoRepository {
final SharedPreferences _prefs;
#visibleForTesting
static const String todoKey = '__todo_key__';
TodoRepositoryImpl({required SharedPreferences prefs}) : _prefs = prefs {
_init();
}
final _controller = StreamController<List<Todo>>();
void _init() {
final result = _prefs.getString(todoKey);
if (result == null || result.isEmpty) return _controller.add([]);
final todos = jsonDecode(result).map((e) => Todo.fromMap(e)).toList();
return _controller.add(todos);
}
#override
Stream<List<Todo>> getTodos() => _controller.stream;
}
todos_overview_store.dart
import 'dart:async';
import 'package:flutter/material.dart';
import '../../domain/entities/todo.dart';
import '../../domain/repositories/todo_repository.dart';
class TodosOverviewStore extends ValueNotifier<List<Todo>> {
final TodoRepository _repository;
late final StreamSubscription<List<Todo>> _subscription;
TodosOverviewStore({required TodoRepository todoRepository})
: _repository = todoRepository,
super([]);
void subscriptionRequested() {
_subscription = _repository.getTodos().listen((event) => value = event);
}
#override
Future<void> dispose() async {
await _subscription.cancel();
super.dispose();
}
}
todos_overview_store_test.dart
import 'dart:async';
import 'package:flutter_test/flutter_test.dart';
import 'package:hiveleak/domain/entities/todo.dart';
import 'package:hiveleak/domain/repositories/todo_repository.dart';
import 'package:hiveleak/presentation/stores/todos_overview_store.dart';
import 'package:mocktail/mocktail.dart';
class TodoRepositoryMock extends Mock implements TodoRepository {}
main() {
final todoRepository = TodoRepositoryMock();
late TodosOverviewStore todosOverviewStore;
final todo = Todo(title: 'lorem ipsum', done: false);
group('subscriptionRequested', () {
when(() => todoRepository.getTodos())
.thenAnswer((_) => Stream.value([todo]));
setUp(() {
todosOverviewStore = TodosOverviewStore(todoRepository: todoRepository);
});
test('It should call todoRepository.getTodos', () {
todosOverviewStore.subscriptionRequested();
verify(() => todoRepository.getTodos()).called(1);
});
test('It should update its value when stream emits', () {
todosOverviewStore.subscriptionRequested();
expect(todosOverviewStore.value, equals([todo]));
});
});
}
Error
But when I run this test, I get the following result:
package:test_api expect
package:flutter_test/src/widget_tester.dart 460:16 expect
test\presentation\stores\todos_overview_store_test.dart 34:7 main.<fn>.<fn>
Expected: [Instance of 'Todo']
Actual: []
Which: at location [0] is [] which shorter than expected
What should I do to make this work ? Thanks in advance.
I am trying to write unit tests in Dart/Flutter for my TextField validations. However, I have a little problem here because the tests are working, but I want to return the value with localization now.
How exactly do I implement this into the tests now?
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class ValidationConstants {
static String? notEmpty(String? value, BuildContext context) {
if (value == null || value.isEmpty) {
return AppLocalizations.of(context)!.text_field_can_not_be_empty;
}
return null;
}
}
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
group('text field validations', () {
test('no empty text validation', () {
const emptyText = '';
const noEmptyText = 'Hello, World!';
// BuildContext is needed here
expect(ValidationConstants.notEmpty(emptyText, [...]).runtimeType, String);
expect(ValidationConstants.notEmpty(noEmptyText, [...]) == null, true);
});
});
}
You can use like below, use setUp() method provided by flutter_test
void main() {
S? mockAppLocal;
setUp(() async {
mockAppLocal = await S.delegate.load(const Locale('en'));
});
}
Trying to study bloc in flutter. I have bloc, events and repository files. I know how to pass data between pages, but i can't pass the parameter to the repository
I need to pass a parameter to the repository for make query to db. For example category id
products_by_category_repository.dart
import 'package:billfort/models/products_model.dart';
import 'package:billfort/strings/strings.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
abstract class ProductsByCategoryRepository {
Future<List<HydraMember>> getProducts();
}
class ProductsByCategoryRepositoryImpl implements ProductsByCategoryRepository {
#override
Future<List<HydraMember>> getProducts() async {
var response = await http.get(Uri.parse("${AppStrings.api}products?category=here need pass id"));
if (response.statusCode == 200) {
var data = json.decode(response.body);
List<HydraMember> products = Products.fromJson(data).hydraMember;
return products;
} else {
throw Exception();
}
}
}
products_bloc.dart
import 'package:bloc/bloc.dart';
import 'package:billfort/bloc/products_event.dart';
import 'package:billfort/bloc/products_state.dart';
import 'package:billfort/models/products_model.dart';
import 'package:billfort/repository/products_repository.dart';
import 'package:meta/meta.dart';
class ProductsBloc extends Bloc < ProductsEvent, ProductsState > {
ProductsRepository repository;
ProductsBloc({
#required this.repository
}): super(null);
#override
// TODO: implement initialState
ProductsState get initialState => ProductsInitialState();
#override
Stream < ProductsState > mapEventToState(ProductsEvent event) async *{
if (event is FetchProductsEvent) {
yield ProductsLoadingState();
try {
List < HydraMember > products = await repository.getProducts();
yield ProductsLoadedState(products: products);
} catch (e) {
yield ProductsErrorState(message: e.toString());
}
}
}
}
If your process of fetching products needs input, then your class FetchProductsEvent should have a property that provides that input, just as your state class ProductsLoadedState for example has a products property that provides the output.
That said, did you upgrade your bloc package without really understanding the changes? Why are you passing null to your base constructor, but still providing an overload for initialState? Your base constructor should get ProductsInitialState() directly and you should remove the overload. If it does not fire warnings for you, you may want to crank up your compiler settings, so your compiler can guide you better.
I'm really confused of how to get the bloc to emit the initial state. bloc.state emits the latest state. And since there is no #override initialState available in the new bloc library, initialState was passed in to the super constructor. But still bloc does not emit the initialState, which in this case is Empty().
number_trivia_bloc.dart
import 'package:clean_architecture/core/error/failure.dart';
import 'package:clean_architecture/core/utils/input_converter.dart';
import 'package:clean_architecture/features/number_trivia/domain/usecases/get_concrete_number_trivia_repository.dart';
import 'package:clean_architecture/features/number_trivia/domain/usecases/get_random_number_trivia.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'number_trivia_state.dart';
import 'number_trivia_event.dart';
const SERVER_FAILURE = 'Server failure';
const CACHE_FAILURE = 'Cache failure';
const INVALID_INPUT_FAILURE =
'Invalid Input Failure - The input should not be a negative integer or zero';
class NumberTriviaBloc extends Bloc<NumberTriviaEvent, NumberTriviaState> {
final GetConcreteNumberTrivia getConcreteNumberTrivia;
final GetRandomNumberTrivia getRandomNumberTrivia;
final InputConverter inputConverter;
NumberTriviaBloc({
// Changed the name of the constructor parameter (cannot use 'this.')
#required GetConcreteNumberTrivia concrete,
#required GetRandomNumberTrivia random,
#required this.inputConverter,
// Asserts are how you can make sure that a passed in argument is not null.
// We omit this elsewhere for the sake of brevity.
}) : assert(concrete != null),
assert(random != null),
assert(inputConverter != null),
getConcreteNumberTrivia = concrete,
getRandomNumberTrivia = random,
super(Empty());
#override
Stream<NumberTriviaState> mapEventToState(
NumberTriviaEvent event,
) async* {
if (event is GetTriviaForConcreteNumber) {
final inputEither =
inputConverter.stringToUnsignedInteger(event.numberString);
yield* inputEither.fold(
(failure) async* {
yield Error(message: INVALID_INPUT_FAILURE);
},
(integer) async* {
yield Loading();
},
);
}
}
}
number_trivia_bloc_test.dart
import 'package:clean_architecture/core/utils/input_converter.dart';
import 'package:clean_architecture/features/number_trivia/data/models/number_trivia_model.dart';
import 'package:clean_architecture/features/number_trivia/domain/entities/number_trivia.dart';
import 'package:clean_architecture/features/number_trivia/domain/usecases/get_concrete_number_trivia_repository.dart';
import 'package:clean_architecture/features/number_trivia/domain/usecases/get_random_number_trivia.dart';
import 'package:clean_architecture/features/number_trivia/presentation/bloc/number_trivia_bloc.dart';
import 'package:dartz/dartz.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:clean_architecture/features/number_trivia/presentation/bloc/number_trivia_state.dart';
import 'package:clean_architecture/features/number_trivia/presentation/bloc/number_trivia_event.dart';
import 'package:mockito/mockito.dart';
class MockGetConcreteNumberTrivia extends Mock
implements GetConcreteNumberTrivia {}
class MockGetRandomNumberTrivia extends Mock implements GetRandomNumberTrivia {}
class MockInputConverter extends Mock implements InputConverter {}
void main() {
NumberTriviaBloc bloc;
MockGetConcreteNumberTrivia mockGetConcreteNumberTrivia;
MockGetRandomNumberTrivia mockGetRandomNumberTrivia;
MockInputConverter mockInputConverter;
setUp(() {
mockGetConcreteNumberTrivia = MockGetConcreteNumberTrivia();
mockGetRandomNumberTrivia = MockGetRandomNumberTrivia();
mockInputConverter = MockInputConverter();
bloc = NumberTriviaBloc(
concrete: mockGetConcreteNumberTrivia,
random: mockGetRandomNumberTrivia,
inputConverter: mockInputConverter,
);
});
test('initialState should be Empty', () {
// assert
expect(bloc.state, equals(Empty()));
});
group('GetTriviaForNumber', () {
String str = '1';
int parsedStr = int.parse(str);
NumberTrivia tTrivia =
NumberTriviaModel(text: 'test text', number: parsedStr);
test('Should convert a string to an unsigned integer', () async {
//arrange
when(mockInputConverter.stringToUnsignedInteger(any))
.thenReturn(Right(parsedStr));
//act
bloc.add(GetTriviaForConcreteNumber(str));
await untilCalled(mockInputConverter.stringToUnsignedInteger(str));
//assert
verify(mockInputConverter.stringToUnsignedInteger(any));
});
test('Should return [Error] for InvalidInputFailure', () async {
//arrange
when(mockInputConverter.stringToUnsignedInteger(any))
.thenReturn(Left(InvalidInputFailure()));
//assert later
final expected = [
Empty(),
Error(message: INVALID_INPUT_FAILURE),
];
expectLater(bloc, emitsInOrder(expected));
//act
bloc.add(GetTriviaForConcreteNumber(str));
});
});
}
It should work but all I get is this error screaming at me. I tries bloc.cast(), bloc.asBroadcastStream(). Nothing seems to work.So, please, can someone help me figure this out. Thanks in advance.
There contains some change from flutter_bloc 6.0.0
You can check the initial state via
test('initial state is correct', () {
expect(bloc.state, Empty());
});
Mentioned here:
Regression: initial state is not emited anymore
I am creating a state machine with Dart,
I am stuck now, I usually did everything in C++, and I would just pass a pointer for a task like this, but in Dart, I am not sure how to do this.
Basically I have created:
class state
{
some methods
}
class MenuState extends State
{
overridden methods
}
now I need that my state machine class would take all children of the state as a correct argument,
So basically what I have written.
class StateMachine
{
StateMachine(State newState)
}
void main()
{
MenuState myMenuState = new MenuSatate();
StateMachine stateM = new StateMachine(myMenuState);
}
I get the error that I cannot pass the myMenuState even it is a child of State. How do I work this around in dart?
The code looks like this
main.dart
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flame/util.dart';
import 'package:flutter/services.dart';
import 'package:flutter/gestures.dart';
import 'package:flame/flame.dart';
import 'stateMachine/gameLoop.dart';
void main() async
{
WidgetsFlutterBinding.ensureInitialized();
Util flameUtil = Util();
await flameUtil.fullScreen();
await flameUtil.setOrientation(DeviceOrientation.portraitUp);
MyGame game = new MyGame();
runApp(game.widget);
}
state.dart
import 'dart:ui';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
class MyState extends StatefulWidget
{
#override
MyState()
{
initialiaze();
}
void initialiaze() async
{
}
void render(Canvas canvas)
{
}
void update(double t)
{
}
void resize(Size size)
{
}
void onTapDown(TapDownDetails d)
{
}
#override
State<StatefulWidget> createState() {
// TODO: implement createState
throw UnimplementedError();
}
}
menuState.dart
import 'package:flutter/material.dart';
import '../stateMAchine/state.dart';
import 'package:flutter/gestures.dart';
import 'dart:ui';
class MenuState extends State<MyState>
{
#override
Widget build(BuildContext context) {
// TODO: implement build
throw UnimplementedError();
}
#override
void initialiaze() async
{
print("menu State initliazed");
}
#override
void render(Canvas canvas)
{
}
#override
void update(double t)
{
}
#override
void resize(Size size)
{
}
#override
void onTapDown(TapDownDetails d)
{
}
}
stateMachine.dart
import "state.dart";
MyState stateReference;
class StateMachine
{
StateMachine(MyState ref)
{
initialiaze(ref);
}
void initialiaze(MyState ref) async
{
ref.initialiaze();
}
}
gameLoop.dart
this is there error comes, then I start to put menuState as an argument
import 'dart:ui';
import 'package:flame/flame.dart';
import 'package:flame/game.dart';
import 'dart:math';
import 'package:flutter/gestures.dart';
import 'stateMachine.dart';
import '../states/menuState.dart';
import 'state.dart';
class MyGame extends Game {
Size screenSize;
double tileSize;
MenuState menuState;
StateMachine stateMachine;
Random rnd;
MyGame()
{
initialiaze();
}
void initialiaze() async
{
resize(await Flame.util.initialDimensions());
rnd = Random();
menuState = new MenuState();
stateMachine = new StateMachine(menuState);
}
void render(Canvas canvas)
{
Rect bgRect = Rect.fromLTWH(0, 0, screenSize.width, screenSize.height);
Paint bgPaint = Paint();
bgPaint.color = Color(0xff576574);
canvas.drawRect(bgRect, bgPaint);
}
void update(double t)
{
}
void resize(Size size)
{
}
void onTapDown(TapDownDetails d)
{
}
}