I want to test my ViewModel that goes to a named route with GoRouter. Therefore, I pass my context from my View to the ViewModel. My problem is that the mocked contains does not contain a GoRouter. I get the error "No GoRouter found in context". How do I mock the context so that I can unit test this?
void main() {
group("MainScreenViewModel Tests", () {
MainScreenViewModel viewModel = MainScreenViewModel();
setUp(() {
viewModel = MainScreenViewModel();
});
test("MoveToTab should change currentIndex value", () {
final context = MockBuildContext();
when(() => context.goNamed("Test")).thenReturn(null); // Here is the problem
viewModel.moveToTab(context, index: 1);
expect(viewModel.currentIndex, 1);
verify(() => context.goNamed(any())).called(1);
verify(() => notifyListenerCallback()).called(1);
});
});
}
The ViewModel
const List<String> routeList = ["/a", "/b"];
class MainScreenViewModel extends ChangeNotifier {
int _currentIndex = 0;
int get currentIndex => _currentIndex;
void moveToTab(BuildContext context, {required int index}) {
_currentIndex = index;
context.goNamed(routeList[index]);
notifyListeners();
}
}
Related
from te first page of Rivepod library I found this example.
final counterProvider = StateNotifierProvider<Counter, int>((ref) {
return Counter();
});
class Counter extends StateNotifier<int> {
Counter() : super(0);
void increment() => state++;
}
Now, what I want to ask is: what is the right way to do something like this below?
class Counter extends StateNotifier<int> {
Counter({int? value}) : super(value ?? 0);
void increment() => state++;
}
I want that my counterProvider can have optional parameters in input, my use case is a StateNotifier used to manage a edit/create page.
You can do like this:
final valueProvider = StateProvider<Counter, int>((ref) => return 5);
final counterProvider = StateNotifierProvider<Counter, int>((ref) {
final value = ref.watch(valueProvider);
return Counter(value);
});
class Counter extends StateNotifier<int> {
Counter(int value) : super(value ?? 0);
void increment() => state++;
}
Also, you can try using the modifier .family
That change state provider you can try like this:
ref.read(valueProvider.notifier).update((state) => 212);
// or use
...
ProviderScope(
overrides: [
valueProvider.overrideWithValue(21332432),
],
child: MyApp(),
),
...
You can see about it here.
to have an optional parameter, this is how to do it:
class Counter extends StateNotifier<int> {
Counter({int value = 0}) : super(value);
void increment() => state++;
}
I'm trying to pass a value of a variable from a StatefulWiget to another StatefulWidget
class InputFieldCheckTick extends StatefulWidget {
double timbreFiscaleFournisseur = 0.000;
bool exoTVA = false;
.
.
.
value: isTicked,
onChanged: (value) async {
await setState(() {
isTicked = value;
if (isTicked == false) {
widget.exoTVA = false;
} else {
widget.exoTVA = true;
}
});
.
.
.
value: isTicked,
onChanged: (value) async {
await setState(() {
isTicked = value;
if (isTicked == false) {
widget.exoTVA = false;
} else {
widget.exoTVA = true;
}
});
and i'm trying to pass the values of exoTVA and timbreFiscaleFournisseur here :
setState(() {
future = ajoutFournisseur(
numeroFournisseur.text,
addressFournisseur.text,
matriculeFiscaleFournisseur.text,
raisonSocialeFournisseur.text,
paysFournisseur.text,
villeFournisseur.text,
InputFieldCheckTick()
.timbreFiscaleFournisseur,
InputFieldCheckTick().exoTVA);
});
I think you are creating an StatefulWidget which contains a Final property called exoTVA, something like:
class MyWidget extends StatefulWidget {
final bool exoTVA;
[...]
Because you are calling widget.exoTVA which refers to stateful related widget, and there is your mistake. You can't change widget.exoTVA for the way widgets are build. What you can do is to do this:
class _MyWidgetState extends State<MyWidget> {
// Here you designate an instance value for your widget property
bool? _exoTVA;
// On initState you define from parent widget, I do this all the time
#override
initState((){
super.initState();
_exoTVA = widget.exoTVA;
});
[...] //Continue with the rest of your widget
Also when you call those changes in setState, change widget.exoTVA to _exoTVA, and to finish you can call that var like this:
setState(() {
future = ajoutFournisseur(
numeroFournisseur.text,
addressFournisseur.text,
matriculeFiscaleFournisseur.text,
raisonSocialeFournisseur.text,
paysFournisseur.text,
villeFournisseur.text,
InputFieldCheckTick()
.timbreFiscaleFournisseur,
_exoTVA);//Changed this line
});
For your reference, check out StatefulWidget class
I am facing very weird problem. i am using bloc with freezed, injectable and dartz. i just need to get data from SQl database and display it when a today page opened.
The code of UI is:
class TodayPage extends HookWidget {
const TodayPage();
#override
Widget build(BuildContext context) {
return BlocProvider<ScheduledNotesCubit>(
lazy:false,
create: (context) => getIt<ScheduledNotesCubit>()
..countDoneNoteOutOfAllNotes()
..retrieveData(),
child: BlocBuilder<ScheduledNotesCubit, ScheduledNotesState>(
builder: (context, state) {
return ListView.builder(
itemCount: state.maybeMap(
orElse: () {}, getNotesCount: (g) => g.noteCount),
itemBuilder: (BuildContext context, int index) {
return Text(
"${state.maybeMap(orElse: () {}, getNotes: (notes) {
return notes.getNotes[index]['content'];
})}",
);
},
);
},
),
);
}
}
The code of state is:
#freezed
class ScheduledNotesState with _$ScheduledNotesState {
const factory ScheduledNotesState.initial() = _Initial;
const factory ScheduledNotesState.getNotes({required List<Map<String, dynamic>> getNotes}) = _GetNotes;
const factory ScheduledNotesState.getNotesCount({required int noteCount}) = _GetNotesCount;
const factory ScheduledNotesState.getCountDoneNoteOutOfAllNotes({required String getCountDoneNoteOutOfAllNotes}) = _GetCountDoneNoteOutOfAllNotes;
const factory ScheduledNotesState.updateIsDoneNote({required int updateIsDoneNote}) = _UpdateIsDoneNote;
}
The code of cubit is:
#injectable
class ScheduledNotesCubit extends Cubit<ScheduledNotesState> {
ScheduledNotesCubit(this._noteRepository)
: super(const ScheduledNotesState.initial());
final NoteRepository _noteRepository;
// retrieve data
void retrieveData() async {
return emit(ScheduledNotesState.getNotes(
getNotes: await _noteRepository.retrieveData()));
}
}
This Cubit Does not return a value in listView, instead it returns null values, But When i try to do so it works!!!!!!
the updated cubit code is:
#injectable
class ScheduledNotesCubit extends Cubit<ScheduledNotesState> {
ScheduledNotesCubit(this._noteRepository)
: super(const ScheduledNotesState.initial());
final NoteRepository _noteRepository;
// retrieve data
void retrieveData() async {
var d= await _noteRepository.retrieveData(); //-->updated
var x= d[1]['content']; //-->updated
print("\n $x \n") ; // -->updated
return emit(ScheduledNotesState.getNotes(
getNotes: await _noteRepository.retrieveData()));
}
}
Can you try add lazy to false for BlocProvider, and update this code:
void retrieveData() {
_noteRepository.retrieveData().then((value) {
emit(ScheduledNotesState.getNotes(getNotes: value));
});
}
The solution is create a data class for the cubit, instead of creating a sealed classes.
I'm currently implementing pagination using pagewise package. But to use the pagecontroller, I have to define a static controller and static future function that will connect to my api using http. My problem is, I also need the current user id as a parameter in my API request retrieved using provider. And I also need the BuildContext to show dialog box on API request return. Is it ok to save the id and context globally or outside the class just like the sample code below? Please teach me how to do this the correct way.
int id;
BuildContext currentContext;
class MyWidgetView extends StatefulWidget {
#override
_MyWidgetViewState createState() => _MyWidgetViewState();
}
class _MyWidgetViewState extends State<MyWidgetView> {
bool _empty = false;
#override
void initState() {
super.initState();
currentContext = context;
id = Provider.of<User>(context, listen: false).id;
this._pageLoadController.addListener(() {
if (this._pageLoadController.noItemsFound) {
setState(() {
this._empty = this._pageLoadController.noItemsFound;
});
}
});
}
final _pageLoadController = PagewiseLoadController(
pageSize: PAGE_SIZE, pageFuture: (pageIndex) => getPage(pageIndex));
static Future<List<page>> getPage(int pageIndex) async {
final APIService _pageService = APIService();
final pages = await _pageService.getPage(id: id);
if (pages.error == false) {
return pages.data;
} else {
dialogBox(
title: 'Error',
message: pages.errorMessage,
context: currentContext,
isModal: true,
function: () => Navigator.pop(currentContext));
}
}
That static is a rabbit hole you should not follow. Just don't make it static in the first place.
PagewiseLoadController _pageLoadController;
#override
void initState() {
super.initState();
_pageLoadController = PagewiseLoadController(pageSize: PAGE_SIZE, pageFuture: (pageIndex) => getPage(pageIndex));
// rest of your method here
}
Now your getPage method can be a normal, non-static class method.
I'm brand new to Flutter / Dart and I'm trying to build a reusable infinite scroller with placeholder loading. The class is as follows:
import 'dart:async';
import 'package:flutter/material.dart';
class PagedScroller<T> extends StatefulWidget {
final int limit;
final Future<List<T>> Function(int, int) getDataFunction;
final Widget Function(T) renderFunction;
final Widget Function() renderPlaceholderFunction;
PagedScroller(
{#required this.limit,
#required this.getDataFunction,
#required this.renderFunction,
#required this.renderPlaceholderFunction});
#override
_PagedScrollerState<T> createState() => _PagedScrollerState<T>();
}
class _PagedScrollerState<T> extends State<PagedScroller> {
int _offset = 0;
int _lastDataLength = 1; // Init to one so the first call can happen
List<dynamic> _items = [];
Future<List<dynamic>> _future;
bool _isInitializing = false;
bool _isInitialized = false;
bool _isLoading = false;
ScrollController _controller =
ScrollController(initialScrollOffset: 0.0, keepScrollOffset: true);
_PagedScrollerState();
void _init() {
_isInitializing = true;
_reset();
_controller.addListener(() {
bool loadMore = false;
if (_controller.position.maxScrollExtent == double.infinity) {
loadMore = _controller.offset == _controller.position.maxScrollExtent;
} else {
loadMore =
_controller.offset >= _controller.position.maxScrollExtent * 0.85;
}
// Only load more if it's not currently loading and we're not on the last page
// _lastDataLength should be 0 if there are no more pages
if (loadMore && !_isLoading && _lastDataLength > 0) {
_offset += widget.limit;
_load();
}
});
_load();
_isInitializing = false;
_isInitialized = true;
}
void _reset() {
// Clear things array and reset inital get-things link (without paging)
setState(() {
_future = _clearThings();
});
// Reload things
// Reset to initial GET link
_offset = 0;
}
void _load() {
setState(() {
_future = _loadPlaceholders();
_future = _loadData();
});
}
Future<List<dynamic>> _clearThings() async {
_items.clear();
return Future.value(_items);
}
Future<List<dynamic>> _loadPlaceholders() async {
// Add 20 empty placeholders to represent stuff that's currently loading
for (var i = 0; i < widget.limit; i++) {
_items.add(_Placeholder());
}
return Future.value(_items);
}
List<dynamic> _getInitialPlaceholders() {
var placeholders = List<dynamic>();
for (var i = 0; i < widget.limit; i++) {
placeholders.add(_Placeholder());
}
return placeholders;
}
Future<List<dynamic>> _loadData() async {
_setLoading(true);
var data = await widget.getDataFunction(widget.limit, _offset);
// When loading data is done, remove any placeholders
_items.removeWhere((item) => item is _Placeholder);
// If 0 items were returned, it's probably the last page
_lastDataLength = data.length;
for (var item in data) {
_items.add(item);
}
_setLoading(false);
return Future.value(_items);
}
void _setLoading(bool isLoading) {
if (!mounted) {
return;
}
setState(() {
_isLoading = isLoading;
});
}
Future<void> _refreshThings() async {
_reset();
_load();
return Future;
}
#override
Widget build(BuildContext context) {
if (!_isInitializing && !_isInitialized) {
_init();
}
return FutureBuilder(
future: _future,
initialData: _getInitialPlaceholders(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
List<dynamic> loadedItems = snapshot.data;
return RefreshIndicator(
onRefresh: _refreshThings,
child: ListView.builder(
itemCount: loadedItems.length,
controller: _controller,
physics: const AlwaysScrollableScrollPhysics(),
itemBuilder: (BuildContext context, int index) {
var item = loadedItems[index];
if (item is _Placeholder) {
return widget.renderPlaceholderFunction();
} else if (item is T) {
// THIS IS THE LINE THAT FAILS
return widget.renderFunction(item);
}
return Text('Unknown item type');
},
),
);
}
return Container();
},
);
}
}
class _Placeholder {}
The line that fails above:
return widget.renderFunction(item);
Fails with the following:
type '(MyModel) => Widget' is not a subtype of type '(dynamic) => Widget'
I understand why this is happening. The compiler can't know that type T from my PagedScroller<T> is the same as type T from _PagedScrollerState<T>. As a result, Dart tries to be helpful and converts my callback function of type Widget Function(T) to Widget Function(dynamic).
I then figured "maybe I can fake it out" with the following since I know the T in PagedScroller<T> and _PagedScrollerState<T> are always the same:
var renderFunction = widget.renderFunction as Widget Function(T);
return renderFunction(item);
Interestingly, this gives me a warning:
Unnecessary cast.
Try removing the cast.
Yet it won't even run that line (crashes) with the following:
Either the assertion indicates an error in the framework itself, or we should provide substantially more information in this error message to help you determine and fix the underlying cause.
In either case, please report this assertion by filing a bug on GitHub:
https://github.com/flutter/flutter/issues/new?template=BUG.md
Changing everything to dynamic works a charm, but I really don't want to lose the readability of generics here if I don't have to.
Despite extensive searching, I can't find the equivalent of C#'s Convert.ChangeType where you can provide types at runtime so I can just do the cast I want and be done with it.
This seems like a really simple thing to achieve, but I'm stuck.
You can consume the scroller with this simple main.dart copy/pasted:
import 'package:flutter/material.dart';
import 'package:minimal_repros/paged_scroller.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
Future<List<MyModel>> getDataFunction(int limit, int offset) async {
var myModels = List<MyModel>();
// Simulate API call
await Future.delayed(Duration(milliseconds: 1000));
for (int i = 0; i < limit; i++) {
var myModel = MyModel();
myModel.count = i + offset;
myModel.firstName = 'Bob';
myModels.add(myModel);
}
return myModels;
}
Widget renderFunction(MyModel myModel) {
return Text(myModel.firstName);
}
Widget renderPlaceholderFunction() {
return Text('Loading');
}
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: PagedScroller(
getDataFunction: getDataFunction,
renderFunction: renderFunction,
renderPlaceholderFunction: renderPlaceholderFunction,
limit: 20));
}
}
class MyModel {
int count;
String firstName;
}
In the declaration of your State class, you forgot to specify the generic parameter of the widget.
Instead of:
class _PagedScrollerState<T> extends State<PagedScroller> {
do:
class _PagedScrollerState<T> extends State<PagedScroller<T>> {