Using deferred libraries with variables - flutter

Sorry in advance I am a beginner and my English is approximate.
I don't know if what I'm doing is correct or not so sorry (again) if this sounds stupid to you.
I try to recover the contents of certain files only when I need them.
I think it's good for optimizing my dart/flutter app and reducing loading times.
So
So I have a list of files coded like this :
N101aze.dart which contains a Map called N101aze
N101qsd.dart which contains a List called N101qsd
N101wxc.dart which contains a List called N101wxc
...
In the same way I have :
N102aze.dart which contains a Map called N102aze
N102qsd.dart which contains a List called N102qsd
N102wxc.dart which contains a List called N102wxc
...
etc...
Each time I have another file called and coded like that :
N101.dart
export "path[...]/N101aze.dart";
export "path[...]/N101qsd.dart";
export "path[...]/N101wxc.dart";
...
Now in a file I want to make a Deferred loading like that :
import "path[...]/N101.dart" deferred as N101; //I know lowercase is better
I load the library when I want by using
greet() async {
await N101.loadLibrary();
}
and later I can call my maps and lists by using
N101.N101aze
N101.N101qsd
It's working perfectly when I use the names listed above.
Now, what I really want is to use a key (called kkey here) to call the library I want when I call onPressed on ElevatedButton :
import 'package:flutter/material.dart';
import 'package:my_app/Models/SetColor.dart';
import "path[...]/N101.dart" deferred as N101; //I know lowercase is better
import "path[...]/N102.dart" deferred as N102;
import "path[...]/N103.dart" deferred as N103;
...
class CustomButton extends StatefulWidget {
const CustomButton({
required this.kkey,
}) : super(key: key);
final String kkey;
#override
State<CustomButton> createState() => _CustomButtonState();
}
class _CustomButtonState extends State<CustomButton> {
Future<void> greet(thekey) async {
await thekey.loadLibrary();
}
#override
Widget build(BuildContext context) {
return ElevatedButton(
child: Text(
widget.kkey
),
onPressed: () async {
await greet(widget.kkey); //it's not working beacause kkey type is String
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => LoadingParts(
part1: // here I want N101.N101aze when kkey="N101" so I want something like kkey.kkeyaze
part2: // here I want N101.N101qsd when kkey="N101" so I want something like kkey.kkeyqsd
...
Note :
I "simplified" the code to keep only the essentials
part1 is a Map and part2 is a List
For the greet function, I think I understand it doesn't work because N101, N102, N103... have no type and my kkey is the type of String but I don't know what is possible to do...
To invoke my maps and my lists, I have no idea what to do... Especially when I want to concatenate kkey with aze (for example).
I know I can use Switch Case Statement which looks at all characters from kkey to return, for example "N101.loadlibrary()" and "N101.N101.aze" but the number of "Switch" and "Case" is too big to be worth it and the number of possibilities may increase as the application grows.
I hope you will understand my problem and I hope it's not too stupid, I'm learning^^.
Thank you for your attention! Bye!

Unfortunately, there is no way to do it dynamically this way i.e. getting a declaration by name. Although Dart is a static language that supports reflection, flutter does not by default. In Flutter the mirrors package is disabled in favour of static optimization. There is also reflectable but it has limitations where it's not possible to reflect a module for example.
So, the way to fix it is to use static code too to load the deferred libs like the following.
On each NXXX.dart file, where XXX is 101, 102, 103 etc., declare the same map. For example, in N101.dart it's going to be like this:
// Create a map with all structures needed
final nmap = {
'aze': N101aze,
'qsd': N101qsd,
'wxc': N101wxc,
};
And then the usage is going to be like this:
class CustomButton extends StatefulWidget {
const CustomButton({
Key? key,
required this.kkey,
}) : super(key: key);
final String kkey;
#override
State<CustomButton> createState() => _CustomButtonState();
}
class _CustomButtonState extends State<CustomButton> {
Future<Map<String, Object>> greet(String thekey) async {
// Load the deferred module and return the structures
switch (thekey) {
case 'N101':
await N101.loadLibrary();
return N101.nmap;
case 'N102':
await N102.loadLibrary();
return N102.nmap;
case 'N103':
await N103.loadLibrary();
return N103.nmap;
default:
assert(false);
throw Exception('$thekey not mapped');
}
}
#override
Widget build(BuildContext context) {
return ElevatedButton(
child: Text(widget.kkey),
onPressed: () async {
// Get the common `nmap` from `greet` by `kkey`
final nmap = await greet(widget.kkey);
if (mounted) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => LoadingParts(
// Use the `nmap` by name
part1: nmap['aze']! as Map,
part2: nmap['qsd']! as List,
part3: nmap['wxc']! as List,
),
),
);
}
},
);
}
}

Related

How do I get JSON data and then place it inside another variable without having to worry about Future method

In my use case, I have to change the layout of the app with JSON data. I have a JSON file which I want to get and use the key values without using the Future method in the next method rather I want to place the mapped JSON and place it in empty curly brackets:
This is the JSON file I grab:
# test_json.json
{
"fontSize" : "10",
"fontFamily" : "A",
"fontWeigth" : "bold",
"fontColor" : "blue"
}
This is the file that grabs the JSON file and maps it:
# get_json.dart
class GetJson{
Future<Map<String, dynamic>> getJson() async {
String jsonData =
await rootBundle.loadString('assets/json/test_json.json');
Map<String, dynamic> data = jsonDecode(jsonData);
return data;
}
}
Then I grab this mapped JSON and I want to place it inside a variable called mappedData and place it inside empty curly brackets. Then I want to get the number with getNumber() and inside this method I convert the type of fontSize from string to double with another custom method called TypeConvertor.getDouble():
class Utility {
var mappedData= {};
setJson() async {
mappedData = await GetJson().getJson();
}
getNumber(String key) {
var data = mappedData;
return TypeConvertor.getDouble(data[key]);
}
}
In my use case, i need to do this like this I have no other choice. I want to explicitly grab the JSON like that and I don't want getNumber() to be a Future. Then i cannot place Utility().getNumber("fontSize") inside a stateful widget because then I have to use setState and I want to avoid that because I will have a lot of keys beside fontSize and so then I have to use setState for every key values. I just want to use Utility().getNumber("fontSize") inside property fontSize and The rest also like this. In my usecase I have to do it like that:
class TestView extends StatefulWidget {
const TestView({Key? key}) : super(key: key);
#override
State<TestView> createState() => _TestViewState();
}
class _TestViewState extends State<TestView> {
#override
Widget build(BuildContext context) {
return Text(
"test",
style: TextStyle(fontSize: Utility().getNumber("fontSize")),
);
}
}
But in my app mappedData gives null. The full error is : Unhandled Exception: type 'Null' is not a subtype of type 'String' and the null value is inside mappedData. I want to grab the json data and place it inside an empty map and use the mapped json from there on. How do i resolve the null execption issue?
EDIT
Change variables to correct versions
Probably it's because you don't call setJson before call getNumber.
The following code is work.
final utility = Utility();
await utility.setJson();
print(utility.getNumber("fontSize"));
If you want to avoid similar mistakes, you have some options as solutions.
Include mappedData to Utility's constructor.
Change getNumber to static, and add argument mappedData.
Use JsonSerializable(It's a little difficult but the best solution.)
I found the solution which is partly contributed by #bakatsuyuki. So i did use await utility.setJson(); but i also initilized it with initState() so field utility has no null value. I also used FutureBuilder() to check if snapshot.hasData and then i display the Text() widget with the data from utilitiy else show empty Container. This way i can resolve the null Exception.
This is the view that worked for me:
class AppView extends StatefulWidget {
const AppView({
Key? key,
}) : super(key: key);
#override
_AppViewState createState() => _AppViewState();
}
class _AppViewState extends State<AppView> {
final utility = Utility();
Future setUtility() async {
await utility.setJson();
}
#override
void initState() {
setUtility();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: FutureBuilder(
future: AppContent().getAppContent(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text("test",
style: TextStyle(
fontSize: utility.getNumber("fontSize"),
));
} else {
return Container();
}
}),
),
);
}
}

Flutter : how to fetch data and manage app state

I'm trying Flutter and I need (I think I do) an app state management to share datas across widgets and dont have to make an http request each time a route is called.
I have Places and Events, so I first load my Places to list them at creation of app state with :
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => AppStateModel()..fetchPlaces(),
...
)
);
}
When I click on a Place, I go on place/id screen and as Places doesnt have events props yet, I'm trying to load them with :
class PlacePageArguments {
final String id;
PlacePageArguments(this.id);
}
class PlacePage extends StatefulWidget {
const PlacePage({Key? key}) : super(key: key);
#override
State<PlacePage> createState() => _PlacePageState();
}
class _PlacePageState extends State<PlacePage> {
String id = '';
#override
Widget build(BuildContext context) {
final args =
ModalRoute.of(context)!.settings.arguments as PlacePageArguments;
return Consumer<AppStateModel>(builder: (context, appState, child) {
id = args.id;
appState.fetchEvents(id);
final place = appState.getPlaceById(id);
return Scaffold(...);
})
}
}
But for sure, as I notifyChange to update widget, It does an infinite loop on fetch events.
What should I do ?
What is the best to achieve something like that, maybe a simple futurBuilder will work, but I want to add events and stay on the same page (add event with modal) and want instant result.
thanks for all
You have two options for bringing in the data for this and none of them require ChangeNotifierProvider.
You can pass data via constructors. This works fine for small widget trees but it can easily get complicated.
You can use Providers. Providers allow you to manage data and functions in one class that stays in one file. For more deals please look here.

Possible to provide a "resolved" StreamProvider to child widgets via nested ProviderScope?

I am trying to inject a "resolved" Riverpod StreamProvider object into the tree below to remove some unnecessary async calls. If my interpretation of the docs is correct, a nested ProviderScope should help with this but I am getting a runtime exception.
My use case: I need to access a user-specific specs object high in the widget tree. Some of the data from that object is required all over the remainder of the app, including as a parameter for any DB operation. The specs object comes from firebase and is retrieved async with a StreamProvider.
Once execution is inside the HomePage widget I know the specs object must be loaded and valid so I don't want to fetch it again as a Stream Provider that needs to handle load and error cases. This is especially true where the specs provider is input to other combined providers as the additional load and error cases add lots of unnecessary complexity.
// Called at the root of the tree to retieve some firestore object
final specsStreamProvider = StreamProvider<Specs?>((ref) {
return ref.read(baseDatabaseProvider).currentSpecs();
});
// Called further down to provide the object that was retrieved
final specsProvider = Provider<Specs>((ref) {
throw UnimplementedError('should have been overwritten');
});
// An example of how content will be retrieved from firestore at HomePage widget and below.
// Having to use specsStreamProvider here quickly turns into a mess.
final recordStreamProvider = StreamProvider.autoDispose<List<Record>>((ref) {
final specs = ref.read<Specs>(specsProvider);
final database = ref.read(contentDatabaseProvider(specs.current!));
return database.recordsStream();
});
class SetupWidget extends ConsumerWidget {
const SetupWidget({Key? key, required this.setupBuilder, required this.homeBuilder}) : super(key: key);
final WidgetBuilder setupBuilder;
final WidgetBuilder homeBuilder;
#override
Widget build(BuildContext context, ScopedReader watch) {
final specsAsyncValue = watch(specsStreamProvider);
return specsAsyncValue.when(
data: (specs) => _data(context, specs),
loading: () => const Scaffold(/.../),
error: (e, __) => Scaffold(/.../),
));
}
Widget _data(BuildContext context, Specs? specs) {
if (specs != null) {
return ProviderScope(
// The plan here is to introduce the resolved specs into the tree below
overrides: [specsProvider.overrideWithValue(specs)],
child: homeBuilder(context),
);
}
return setupBuilder(context);
}
}
According to the Riverpod API a nested ProviderScope is a valid tool to overwrite providers for part of the widget tree. Unfortunately, in my case I get a runtime error 'Unsupported operation: Cannot override providers on a non-root ProviderContainer/ProviderScope'
I also tried to make specsProvider a ScopedProvider but then the combined recordStreamProvider doesn't compile. ('error: The argument type 'ScopedProvider' can't be assigned to the parameter type 'RootProvider<Object?, Specs>'.'
I think I figured it out. I made specsProvider a ScopedProvider set in the parent and changed recordStreamProvider (the one that is called in the children only) to not depend on the scoped provider directly.
However, I would still love to hear from one of the Riverpod experts if what I am doing here is acceptable and no anti-pattern.
Parent setting scoped provider:
final specsStreamProvider = StreamProvider<Specs?>((ref) {
return ref.read(baseDatabaseProvider).currentSpecs();
});
// Called further down to provide the object that was retrieved
// This MUST be a ScopedProvider
final specsProvider = ScopedProvider<Specs>((ref) {
throw UnimplementedError('should have been overwritten');
});
class SetupWidget extends ConsumerWidget {/* as before */}
Children consuming scoped provider
// no dependency on specsProvider here
final recordStreamProvider = StreamProvider.family.autoDispose<List<Record>, String>((ref, storeId) {
final database = ref.read(contentDatabaseProvider(storeId));
return database.recordsStream();
});
class HomePage extends ConsumerWidget {
#override
Widget build(BuildContext context, ScopedReader watch) {
final specs = watch(specsProvider);
final recordsAsyncValue = watch(recordsStreamProvider(specs.storeId!));
return recordsAsyncValue.when(
data: (records) => /* build a list */
loading: () => /* show a progress indicator */,
error: (e, __) => /* show an alert dialog */,
));
}
}

Flutter How to get the value of a Provider out

I have this Provider in my code. its suppose to return list
class ServingProvider extends ChangeNotifier {
List<Serving> servings = [];
getServings() {
Firestore.instance
.collection('servings')
.getDocuments()
.then((value) => value.documents)
.then((value) =>
value.map((serving) => Serving.fromJson(serving.data)).toList())
.then((value) => servings.addAll(value))
.then((value) => notifyListeners());
print(servings);
}
// List<Serving> get servings => servings;
}
I want to get the value here but I'm getting null for some reason.
class KMultiScrollWheel extends StatelessWidget {
final String title;
final String value;
final BuildContext passedContext;
final String dialogTitle;
KMultiScrollWheel({
#required this.title,
this.value,
#required this.passedContext,
this.dialogTitle,
});
#override
Widget build(BuildContext context) {
final servings = Provider.of<ServingProvider>(context).servings;
print(servings)
This is the wrapper of my class.
ChangeNotifierProvider(
create: (_) => ServingProvider(),
child: KMultiScrollWheel(
title: 'Serving Size',
passedContext: context,
dialogTitle: 'Select Serving',
),
),
I'm coming from react and I'm completely lost for something that should be rather simple. thanks in advance.
One last question whats the point of using get method when I can access the servings already declared at the top.
As you are using realtime database, use StreamProvider instead of ChangeNotifierProvider , this medium article can help you out
I am new to this and can't comment since I am not sure if this will work, but I don't see you calling the getServings method, so maybe your list isn't filled yet?
You could try something like this:
final servingsProvider = Provider.of<ServingProvider>(context);
(await) servingsProvider.getServings(); //and make getServings() async since it connects to Firebase
print(servingsProvider.servings);
Of course you should handle this in a future or streambuilder, to render it.
I think getServings is a Future function, so you must handle the case when servings is null. You can simply add getServings in the constructor of ServingProvider:
class ServingProvider extends ChangeNotifier {
ServingProvider(){
// call getServings in constructor, notify listener when servings update
getServings();
}
...
and handle null case of servings in KMultiScrollWheel build by your own.
The other ways are FutureProvider (set initialData when data is not ready) or StreamProvider.

Flutter: A variable passed to the next screen becomes null

I want to pass a variable to the next screen but it becomes null in the next screen. what am I wrong with this? My code is like below.
first_screen.dart
onTap: () {
print(doc); // Prints out Instance of `DocumentSnapshot` on log
Navigator.of(context).pushNamed('/second', arguments: doc);
},
second_screen.dart
class SecondScreen extends StatefulWidget {
final DocumentSnapshot doc;
SecondScreen({
this.doc,
});
#override
State<StatefulWidget> createState() {
return _SecondScreenStateState();
}
}
class _SecondScreenState extends State<SecondScreen> {
#override
void initState() {
super.initState();
print(widget.doc); // Prints out null here
}
I tried with othe data types but all variables become null in the next screen.
You have to pass argument like this:
Navigator.of(context).pushNamed('/second', arguments: doc);
for you is true but, use the ModalRoute.of() method to returns the current route with the arguments like this:
final doc = ModalRoute.of(context).settings.arguments as String;
I assumed that doc is String.
If you're sharing data between widgets (screens) try looking at InheritedWidget.Take a look at https://www.youtube.com/watch?v=1t-8rBCGBYw. You can also look at state management packages like provider which is pretty easy to understand or a bloc. These will save you in the long run.