How can I import Riverpod StateNotifier from separate file? - flutter

- src/main.dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
class AnotherWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Consumer(
builder: (context, watch, _) {
},
);
}
}
I want to take city to another file with other StateProviders.
- src/controller/states.dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
final city = StateProvider<String>((ref) => 'NYC');
final currency = StateProvider<String>((ref) => 'USD');
If I take final city from main.dart and move it to states.dart, how can I make them available to main.dart? I've tried to access these states, but it does not recognize it.

Related

unable to pass instance to the initializer [duplicate]

This question already has answers here:
Error: The instance member ... can't be accessed in an initializer
(4 answers)
Closed 3 months ago.
Error : The instance member 'widget' can't be accessed in an initializer.
Im creating a bar chart with getx controller, i want to retrieve values from firebase and pass it to barchart to show it to the user. But the main problem here is that the variable of string could not pass into the controller, can i have a guidance on how to pass it? none of the guidance help me, i really need the help
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:charts_flutter/flutter.dart' as charts;
import 'package:fyp/storage/OrderStats.dart';
import 'package:fyp/storage/OrderStatsController.dart';
import 'package:get/get.dart';
class testChart extends StatefulWidget {
final String? salesDate;
testChart({required this.salesDate});
#override
State<testChart> createState() => _testChartState();
}
class _testChartState extends State<testChart> {
String sales = "11.2022 Sales";
final OrderStatsController orderStatsController = Get.put(OrderStatsController(salesDate: '11.2022 Sales'));
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Bar Chart'),
),
body: SizedBox(height: 300,
child:
FutureBuilder(
future: orderStatsController.stats.value,
builder: (BuildContext context, AsyncSnapshot<List<OrderStats>>
snapshot){
if(snapshot.hasData){
return Container(
height: 250,
child: CustomBarChart(orderStats: snapshot.data!, sales: widget.salesDate.toString()),
);
}
else if(snapshot.hasError){
return Text('${snapshot.error}');
}
else{
return Center(child: CircularProgressIndicator(),);
}
},
)
// CustomBarChart(orderStats: OrderStats.data,),
),
);
}
}
class CustomBarChart extends StatefulWidget {
CustomBarChart({Key? key, required this.orderStats, required this.sales}) : super(key: key);
final List<OrderStats> orderStats;
final String sales;
#override
State<CustomBarChart> createState() => _CustomBarChartState();
}
class _CustomBarChartState extends State<CustomBarChart> {
late String salesDate = '11.2022 Sales';
final OrderStatsController orderStatsController = Get.put(OrderStatsController(salesDate: widget.sales.toString()));
#override
Widget build(BuildContext context) {
List<charts.Series<OrderStats, String>> series = [
charts.Series(
id: 'sales',
data: widget.orderStats,
domainFn: (series, _) => series.serviceName.toString(),
measureFn: (series, _) => series.sales,
)
];
return charts.BarChart(series, animate: true,);
}
}
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:fyp/storage/OrderStats.dart';
import 'package:get/get.dart';
import 'storageService.dart';
class OrderStatsController extends GetxController{
final String salesDate;
OrderStatsController({required this.salesDate});
final Storage storage = Storage();
var stats = Future.value(<OrderStats>[]).obs;
#override
void onInit(){
stats.value = FirebaseFirestore.instance.
collection(salesDate).get().then((querySnapshot) =>
querySnapshot.docs.asMap().entries.map((entry) =>
OrderStats.fromSnapshot(entry.value, entry.key)).toList());
super.onInit();
}
}
right now i only tried passing just "sales", it is fixed, i cannot pass in any variable such as String type
You can define your controller like this:
late OrderStatsController orderStatsController;
then pass your value in initState :
#override
void initState() {
super.initState();
orderStatsController = Get.put(OrderStatsController(salesDate: sales));
}

How do I handle screen parameters in themedata's routes?

I want to use ThemeData and set routes at
routes: {
'/': (context) => const LoginScreen(),
'/sign-up-screen': (context) => const SignUpScreen(),
'/sign-in-screen': (context) => const SignInScreen(),
'/map-screen': (context) => MapScreen()}
But I also have a screen with parameters, like this.
import 'dart:io';
import 'package:dash_mement/poststory/check_image.dart';
import 'package:dash_mement/providers/pushstory_provider.dart';
import 'package:dash_mement/style/mmnt_style.dart';
import 'package:dash_mement/style/story_textstyle.dart';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'package:provider/provider.dart';
class PostImage extends StatelessWidget {
Widget _inform = Image.asset("assets/images/check_image.png");
final ImagePicker _picker = ImagePicker();
late Function _backButton;
double? lat_y;
double? lng_x;
PostImage(this._backButton) {}
PostImage.newPin({required double latitude_y, required double longitude_x}) {
lat_y = latitude_y;
lng_x = longitude_x;
}
....
}
How can I add this screen at ThemeData's routes of main.dart
Add it like any other route and access arguments in a widget something like this:
class PostImage extends StatelessWidget {
...
#override
Widget build(BuildContext context) {
final args = ModalRoute.of(context)!.settings.arguments;
Or alternatively use onGenerateRoute to handle routes and extract arguments and pass to a widget.
See Pass arguments to a named route for details.

Unit-testing function with isolates and compute in flutter

I'm trying to test a widget that receives and displays some data. This widget uses a controller. In the constructor I start receiving data, after which I execute the parser in a separate isolate. During the tests, the function passed to the compute is not executed until the end, and the widget state does not change. In fact, the structure of the widget looks a little more complicated, but I wrote smaller widget that saves my problem:
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:rxdart/rxdart.dart';
class TestObj {
int id;
String name;
String number;
TestObj(this.id, this.name, this.number);
static List<TestObj> jsonListParser(String data) {
List mapObjs = json.decode(data) as List;
if (mapObjs.isEmpty) return [];
List<TestObj> testObjs = [];
for (final Map mapObj in mapObjs as List<Map>)
testObjs.add(
TestObj(
mapObj['id'] as int,
mapObj['name'] as String,
mapObj['number'] as String,
),
);
return testObjs;
}
}
class TestController {
final BehaviorSubject<List<TestObj>> testSubj;
final String responseBody =
'[{"id":2,"number":"1","name":"Объект 1"},{"id":1,"number":"2","name":"Объект 2"}]';
TestController(this.testSubj) {
getData(responseBody, testSubj);
}
Future<void> getData(
String responseBody, BehaviorSubject<List<TestObj>> testSubj) async {
List<TestObj> data = await compute(TestObj.jsonListParser, responseBody);
testSubj.sink.add(data);
}
}
class TestWidget extends StatelessWidget {
final BehaviorSubject<List<TestObj>> testSubj;
final TestController controller;
const TestWidget(this.testSubj, this.controller);
#override
Widget build(BuildContext context) {
return StreamBuilder<List<TestObj>>(
stream: testSubj.stream,
builder: (context, snapshot) => snapshot.data == null
? const CircularProgressIndicator()
: ListView.builder(
itemBuilder: (context, index) => Text(snapshot.data[index].name),
),
);
}
}
void main() {
testWidgets('example test', (tester) async {
final BehaviorSubject<List<TestObj>> testSubj =
BehaviorSubject.seeded(null);
final TestController testController = TestController(testSubj);
await tester.pumpWidget(
TestWidget(testSubj, testController),
);
expect(find.byType(CircularProgressIndicator), findsNothing);
});
}
I have tried using tester.pump, tester.pumpAndSettle (crashed by timeout) and tester.runAsync, but so far without success. What are the solutions of this problem?
As indicated in runAsync docs, it is not supported to have isolates/compute in tests that are proceeded by pump().
To make a self-contained solution, check if you run in test environment or not in your code and skip isolates when you run in a test:
import 'dart:io';
if (!kIsWeb && Platform.environment.containsKey('FLUTTER_TEST')) {
calc()
} else {
calcInIsolate()
}

'BuildContext' can't be assigned to the parameter type 'Widget Function(BuildContext, AsyncSnapshot<Position>) Flutter

Noob here. I have been following some coding tutorial on youtube using geolocator plugin and the goal is to track and stream a moving device current location. I can't get it to work because of recent version changes not applying to the way it was coded on the videos. Aside from my very shallow understanding of streams in Flutter.
From my geolocator_service.dart class/file I would like to call and attach it to my map.dart screen file. But in building the widget I got stuck in the builder parameter of StreamBuilder:
import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
import 'package:maglako/services/geolocator_service.dart';
import 'package:provider/provider.dart';
class Map extends StatefulWidget {
#override
_MapState createState() => _MapState();
}
class _MapState extends State<Map> {
final GeolocatorService geolocatorService = GeolocatorService();
#override
Widget build(BuildContext context) {
return Scaffold(
body: StreamBuilder <Position> (
stream: geolocatorService.getCurrentLocation(),
builder: (context, snapshot) // <---- this is where the error is located
) ,
),
);
}
}
My geolocator_service.dart file (geolocator 7.1.0 plugin)
import 'dart:async';
import 'package:geolocator/geolocator.dart';
class GeolocatorService {
final Geolocator geo = Geolocator();
Stream<Position> getCurrentLocation() {
return Geolocator.getPositionStream(desiredAccuracy: LocationAccuracy.high,distanceFilter: 10);
}
Would also appreciate if you can point me to a good tutorial on the subject. Thanks!
Change to this, the builder expect a widget, and in this widget you can draw what you need to show to the user, like lat and lang as text or a custom widget, snapshot will show you the data you need
Widget build(BuildContext context) {
return Scaffold(
body: StreamBuilder <Position> (
stream: geolocatorService.getCurrentLocation(),
builder: (context, snapshot){
// Updated
return Text('Data updated');
}
) ,
),
);
}

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.