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),
);
});
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.
All of the sudden I am getting the error DataStore plugin has not been added to Amplify, recoverySuggestion: Add DataStore plugin to Amplify and call configure before calling DataStore related APIs to rule out any of the work I was doing on that page I tried it on a fresh page with the same result.
I already did execute amplify codegen models, amplify pull and amplify env pull. Also tried to do a flutter clean but I don't see any change at all. I'm really puzzled and can't seem to figure out the issue.
One thing I did notice while debugging was that the initState of the screen seems to be executed earlier as the configureAmplify callback.
I will show the relevant parts of the code (sorry for the long code in advance).
Pubspec.yaml
dependencies:
...
amplify_flutter: ^0.2.10
amplify_datastore: ^0.2.10
amplify_api: ^0.2.10
amplify_auth_cognito: ^0.2.10
amplify_storage_s3: ^0.2.10
main.dart
import 'package:flutter/material.dart';
import 'package:my_package/screens/main/teams_screen.dart';
import 'package:my_package/services/amplify_services.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
void initState() {
super.initState();
AmplifyService.configureAmplify();
}
#override
Widget build(BuildContext context) {
...
}
}
services/amplify_services.dart
import 'package:flutter/foundation.dart';
import 'package:amplify_flutter/amplify.dart';
import 'package:amplify_datastore/amplify_datastore.dart';
import 'package:amplify_api/amplify_api.dart';
import 'package:amplify_auth_cognito/amplify_auth_cognito.dart';
import 'package:amplify_storage_s3/amplify_storage_s3.dart';
import 'package:my_package/models/ModelProvider.dart';
import 'package:my_package/amplifyconfiguration.dart';
class AmplifyService {
static configureAmplify() async {
AmplifyAPI apiPlugin = AmplifyAPI();
AmplifyAuthCognito authPlugin = AmplifyAuthCognito();
AmplifyStorageS3 amplifyStorageS3 = AmplifyStorageS3();
AmplifyDataStore dataStorePlugin = AmplifyDataStore(
modelProvider: ModelProvider.instance,
);
await Amplify.addPlugins([
dataStorePlugin,
authPlugin,
amplifyStorageS3,
apiPlugin,
]);
try {
await Amplify.configure(amplifyconfig);
} on AmplifyAlreadyConfiguredException {
if (kDebugMode) {
print(
"Amplify was already configured. Looks like app restarted on android.");
}
}
}
}
Lastly the very basic page with not even an output (screens/teams_screen.dart)
import 'dart:async';
import 'package:amplify_datastore/amplify_datastore.dart';
import 'package:amplify_flutter/amplify.dart';
import 'package:flutter/material.dart';
import 'package:my_package/models/Team.dart';
class TeamsScreen extends StatefulWidget {
const TeamsScreen({Key? key}) : super(key: key);
#override
_TeamsScreenState createState() => _TeamsScreenState();
}
class _TeamsScreenState extends State<TeamsScreen> {
late StreamSubscription<QuerySnapshot<Team>> _teamsSubscription;
bool _isLoading = true;
List<Team> teams = [];
#override
void initState() {
super.initState();
_initializeApp();
}
#override
void dispose() {
_teamsSubscription.cancel();
super.dispose();
}
Future<void> _initializeApp() async {
_teamsSubscription = Amplify.DataStore.observeQuery(Team.classType)
.listen((QuerySnapshot<Team> snapshot) {
setState(() {
if (_isLoading) _isLoading = false;
teams = snapshot.items;
});
});
}
#override
Widget build(BuildContext context) {
return Container();
}
}
New day, fresh mind. The issue turned out to be quite simple, I didn't set an _isLoading state to indicate weter or not the configureAmplify callback was completed and let the app just continue loading all the other screens triggering the error. So after setting the state and only adding the rest of the app after the state was changed it worked without any problem.
To fix it I did the following:
import 'package:flutter/material.dart';
import 'package:my_package/screens/main/teams_screen.dart';
import 'package:my_package/services/amplify_services.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
void initState() {
super.initState();
_initializeApp();
}
Future<void> _initializeApp() async {
await AmplifyService.configureAmplify(); // note the await!
setState(() {
_isLoading = false; // important to set the state!
});
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: _isLoading
? Center(child: CircularProgressIndicator())
: const MainScreen(), // _isLoading is very important here.
);
}
}
Here is code from one of the screens where I'm trying to use didChaneDependencies:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:shop/screens/cart_screen.dart';
import '../widgets/products_grid.dart';
import '../widgets/badge.dart';
import '../widgets/app_drawer.dart';
import '../providers/products.dart';
import '../providers/cart.dart';
enum FilterOptions {
Favorites,
All,
}
class ProductsOverviewScreen extends StatefulWidget {
#override
_ProductsOverviewScreenState createState() => _ProductsOverviewScreenState();
}
class _ProductsOverviewScreenState extends State<ProductsOverviewScreen> {
var _showOnlyFavorite = false;
var _isInit = true;
#override
void initState() {
print('initState');
super.initState();
}
#override
void didChaneDependencies() {
print('didChaneDependencies');
super.didChangeDependencies();
}
Problem is that it does not fires up.
And the compiler highlights it with the following message:
The method doesn't override an inherited method. Try updating this
class to match the superclass, or removing the override annotation.
I did try remove the #override , but that did not fixed the problem, I cannot see
print('didChaneDependencies'); in the debugger.
Fix the spelling of the function name, You are missing a G in the didChangeDependencies function name
#override
void didChangeDependencies() {
....
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)
{
}
}
I am trying to see if an id key is available in my app's shared_pereferences and if there is, redirect my user to the homepage. I am checking the Id in the initState() function of my main.dart and I know that the id exists because I can get it in other pages. but in my main.dart it returns null. any ideas?
here is my main.dart code:
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import './ui/pages/auth/auth_one.dart';
import './ui/pages/main_page.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() {
SystemChrome.setPreferredOrientations(
[DeviceOrientation.portraitDown, DeviceOrientation.portraitUp])
.then((_) => runApp(MyApp()));
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String userId;
#override
void initState() {
_getUserId().then((id) => userId = id);
super.initState();
}
#override
Widget build(BuildContext context) {
print(userId);
return MaterialApp(
theme: ThemeData(primarySwatch: Colors.deepPurple),
debugShowCheckedModeBanner: false,
home: userId == null ? AuthOne() : MainPage(),
);
}
_getUserId() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
var id = prefs.getString('id');
return id;
}
}
Your _getUserId method is async, so you will have to refresh the widget after you get the result.
Use this:
#override
void initState() {
_getUserId().then((id) {
//calling setState will refresh your build method.
setState(() {
userId = id;
});
});
super.initState();
}
This is happening because you are trying to use the value before its calculated.
you could use timer function for delay