How to test a ValueNotifier value from a stream? - flutter

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.

Related

How to access another class from a different file in the same flutter project?

Hello everyone please i just started learning and working on flutter some few months ago,i am now trying my hands on it.
I want to know if there is a way that will enable me to access the class Position in the main.dart from the request.dart in such that the part where i add my api in the Uri.parse() the ${postion.latitude} and${position.longitude} won't be Undefined and refer to the Position class in the main.dart file. Thank you!
main.dart file
'''
import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:provider/provider.dart';
import 'package:rider_app/AllScreens/searchScreen.dart';
import 'package:rider_app/AllWidgets/Divider.dart';
import 'package:rider_app/Assistants/assistantMethods.dart';
import 'package:rider_app/DataHandler/appData.dart';
class MainScreen extends StatefulWidget
{
static const String idScreen = "mainScreen";
const MainScreen({Key key}) : super(key: key);
#override
_MainScreenState createState() => _MainScreenState();
}
class _MainScreenState extends State<MainScreen>
{
Completer<GoogleMapController> _controllerGoogleMap = Completer();
GoogleMapController newGoogleMapController;
GlobalKey<ScaffoldState> scaffoldKey = new GlobalKey<ScaffoldState>();
Position currentPosition;
var geoLocator = Geolocator();
double bottomPaddingOfMap=0;
void locatePosition() async
{
Position position = await Geolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.high);
currentPosition= position;
LatLng latLatPosition = LatLng(position.latitude, position.longitude);
//Camera move
CameraPosition cameraPosition = new CameraPosition(target: latLatPosition, zoom: 16);
newGoogleMapController.animateCamera(CameraUpdate.newCameraPosition(cameraPosition));
//newGoogleMapController.animateCamera(CameraUpdate.newCameraPosition(cameraPosition));
String address = await AssistantMethods.searchCoordinateAddress(position, context);
}
'''
request.dart file
'''
import 'dart:convert';
import 'dart:html';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:geolocator/geolocator.dart';
import 'package:http/http.dart' as http;
import 'package:rider_app/configMaps.dart';
import 'package:rider_app/lib/AllScreens/mainscreen.dart';
class RequestAssistant
{
static Future<dynamic> getRequest(url) async
{
var url = Uri.parse("https://maps.googleapis.com/maps/api/geocode/json?latlng=${position.latitude},${position.longitude}&key=$mapKey");
http.Response response = await http.get(url);
try
{
if(response.statusCode==200)
{
String jSonData = response.body;
var decodeData = jsonDecode(jSonData);
return decodeData;
}
else
{
return "failed";
}
}
catch(exp)
{
return "failed";
}
}
}'''
You can pass in getRequest method life
getRequest(Position position) async {...}

Provider dont update a data in Flutter

I'm create a project on Flutter. And I'm using a provider to change screens in my app.
Here is my main.dart file:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:school_app/services/auth_service.dart';
import 'package:school_app/wrapper.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => AuthService().auth,
child: MaterialApp(
home: Wrapper(),
),
);
}
}
Also this is my wrapper.dart file where the screens choose:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:school_app/screens/authenticate/auth.dart';
import 'package:school_app/models/user.dart';
import 'package:school_app/screens/school/home.dart';
import 'package:school_app/services/auth_service.dart';
class Wrapper extends StatelessWidget {
#override
Widget build(BuildContext context) {
final user = Provider.of<AuthProvider>(context);
print(user.auth);
if(!user.auth) return Auth();
return Home();
}
}
And it is my AuthProvider class:
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
class AuthService {
/* AuthUser _user(User user) {
return user != null ? AuthUser(uid: user.uid) : null;
}*/
AuthProvider auth = new AuthProvider();
//sign in
Future signIn(String username, String password) async {
try {
SharedPreferences prefs = await SharedPreferences.getInstance();
var dio = Dio();
Response user = await dio.post('url', data: {
'username': username,
'password': password
});
if(user.data['success'] == false) return user.data['msg'];
await prefs.setString('token', user.data['token']);
auth.setAuth(true);
print("SUCCESS");
} catch(e) {
print('Error ' + e.toString());
}
}
}
class AuthProvider with ChangeNotifier {
bool _auth;
AuthProvider() {
_auth = false;
}
bool get auth => _auth;
void setAuth(bool auth) {
_auth = auth;
notifyListeners();
}
}
And when I call a function in AuthProvider class setAuth, nothing changed. Can you help me and find my mistake?
EDIT
I'm making all changes that you writes but it is not working. Here is my main.dart:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:school_app/services/auth_service.dart';
import 'package:school_app/wrapper.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => AuthProvider(),
child: MaterialApp(
home: Wrapper(),
),
);
}
}
Also wrapper.dart:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:school_app/screens/authenticate/auth.dart';
import 'package:school_app/screens/school/home.dart';
import 'package:school_app/services/auth_service.dart';
class Wrapper extends StatefulWidget {
#override
_WrapperState createState() => _WrapperState();
}
class _WrapperState extends State<Wrapper> {
#override
void initState() {
// TODO: implement initState
super.initState();
AuthService().auth;
}
#override
Widget build(BuildContext context) {
return Consumer<AuthProvider>(builder: (context, authProvider, child) {
print(authProvider.auth);
if (!authProvider.auth) {
return Auth();
} else {
return Home();
}
});
}
}
And AuthService and AuthProvider classes:
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
class AuthService {
/* AuthUser _user(User user) {
return user != null ? AuthUser(uid: user.uid) : null;
}*/
AuthProvider auth = new AuthProvider();
//sign in
Future signIn(String username, String password) async {
try {
SharedPreferences prefs = await SharedPreferences.getInstance();
var dio = Dio();
Response user = await dio.post('url', data: {
'username': username,
'password': password
});
if(user.data['success'] == false) return user.data['msg'];
await prefs.setString('token', user.data['token']);
auth.setAuth(true);
print("SUCCESS");
} catch(e) {
print('Error ' + e.toString());
}
}
}
class AuthProvider with ChangeNotifier {
bool _auth;
AuthProvider() {
_auth = false;
}
bool get auth => _auth;
void setAuth(bool auth) {
_auth = auth;
notifyListeners();
}
}
Notice, that here two classes and in AuthService I'm calling function .setAuth(true).
In your current implementation of Wrapper, you are rendering the widget once and not listening to whether the values changed. You could use Consumer as suggested above. You could also choose to watch the value for changes - like this:
class Wrapper extends StatelessWidget {
#override
Widget build(BuildContext context) {
final user = context.watch<AuthProvider>();
print(user.auth);
if(!user.auth) return Auth();
return Home();
}
}
When you use a watch or Consumer pattern, the widget will be rendered when the values of the underlying store (which is AuthProvider here) gets changed.
The only missing part here is that you never Consume the AuthProvider to listen to the notifyListeners() trigger.
The correct implementation looks like the following (I didn't try it, you may have to correct some typo errors, but you'll get the idea !)
class Wrapper extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Consumer<AuthProvider>(
builder: (context, authProvider, child) {
if (!authProvider.auth) {
return Auth();
} else {
return Home();
}
}
);
}
}
EDIT
I didn't notice you weren't injecting the right Class in your ChangeNotifierProvider. You'll also have to update your widget MyApp
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => AuthProvider(),
child: MaterialApp(
home: Wrapper(),
),
);
}
And in this case you probably should transform your Wrapper widget to a Stateful widget, and in the initState method you should call AuthService().auth.
I strongly recommend you to read the official documentation of Provider, looks like things aren't crystal clear yet in your mind
EDIT 2
You're still missing the point of the Provider library.
The goal of this lib is to provide an instance of a class to your widget tree so you don't have to re-create an instance in each widget.
Here, in AuthService class you're re-creating a AuthProvider with AuthProvider auth = new AuthProvider(); instead of referring to the existing instance created in the parent Widget.
To refer to a previously created instance, you should use Provider.of<AuthProvider>(context); in the AuthService class, or, even better, pass the instance of AuthProvider as a parameter in the signIn method.

Flutter bloc test -- emitsInorder does not emit the initial state

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

Could not called initState and update (riverpod state_notifier)

Problem
I'm using riverpod and state_notifier.
The initState() and update() that StateNotifier has are called and No. The other member functions can be called successfully. However, other member functions can be called successfully.
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:riverpod_todo_list/todo_list_notifier.dart';
import 'package:riverpod_todo_list/todo_list_state.dart';
void main() {
print('start~~');
runApp(ProviderScope(child: MyApp()));
}
class MyApp extends HookWidget {
// ...
}
final todoListProvider = StateNotifierProvider((_) => TodoListNotifier());
class MyHomePage extends HookWidget {
final _controller = TextEditingController();
final todoListNotifier = useProvider(todoListProvider);
final TodoListState _todoListState =
useProvider(todoListProvider.state.select((value) => value));
//...
import 'package:riverpod_todo_list/todo.dart';
import 'package:riverpod_todo_list/todo_list_state.dart';
import 'package:state_notifier/state_notifier.dart';
import 'package:uuid/uuid.dart';
class TodoListNotifier extends StateNotifier<TodoListState> with LocatorMixin {
TodoListNotifier() : super(const TodoListState());
Uuid _uuid = Uuid();
// could not run.
#override
void initState() {
super.initState();
print('init state~~~');
}
// could not run.
#override
void update(Locator watch) {
super.update(watch);
print('update');
}
// could run.
void add(String title) {
Todo todo = Todo(id: _uuid.v4(), title: title);
List<Todo> todoList = []..addAll(state.todoList);
todoList.add(todo);
state = state.copyWith(todoList: todoList);
}
// could run.
void toggleStatus(int index) {
List<Todo> todoList = []..addAll(state.todoList);
todoList[index] = state.todoList[index]
.copyWith(completed: !state.todoList[index].completed);
state = state.copyWith(todoList: todoList);
print('changed toggle~~');
}
}
restarted logs
not put initState() and update() logs.
Performing hot restart...
Restarted application in 464ms.
flutter: start~~
The question is already answered on the Github.
LocatorMixin is not supported by Riverpod.
https://github.com/rrousselGit/river_pod/issues/75#issuecomment-671255330
And it's proposed to note it in the document.
In my opinion, LocatorMixin is not needed to use with Riverpod because of ProvidierReference.
final userRepositoryProvider = Provider((ref) => UserRepository());
final userControllerProvider = StateNotifierProvider((ref) {
return UserController(
// Read userRepositoryProvider and create a UserController from the result
repository: ref.watch(userRepositoryProvider),
);
});

How to pass children class then parent class is required in constructor

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)
{
}
}