Flutter: Convert String to class field name during runtime - flutter

Is it possible to "convert" a String which is dynamically generated in my code, into a class field/variable name?
The issue I am facing is that I have a database which returns a unique name (as String) for each of its rows. I want to use this name in order to find the corresponding field (with the same spelling as the database entry) of a generated class that holds all my translations within its various fields. As this class is generated and subject to constant change there is no way to convert into a Map and thereby access its fields as values as explained here.
My code:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
// Suppose this is a dynamic list returned from a database query
final List<String> listFromDB = ['one', 'three', 'two'];
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView.builder(
itemCount: listFromDB.length,
itemBuilder: (context, index) =>
_listItemBuilder(context, index, listFromDB)),
);
}
}
Widget _listItemBuilder(BuildContext context, int index, List<String> listFromDB) {
// suppose this is some map with translations of different languages.
final labels = Labels.translations['languageCode'];
final String dbUniqueName = listFromDB[index];
// QUESTION: How can I use the above String dbUniqueName to access a field of the
// AnyClass class in order to return the corresponding value, e.g. "eins" for
// its name "one"?
print(labels.dbUniqueName) // ERROR: Not compiling because dbUniqueName is a String
return null;
}
// GENERATED: The below code can not be changed as it is generated
class Labels {
static final Map<String, AnyClass> translations = {
'languageCode' : AnyClass(one: 'eins', two: 'zwei', three: 'drei')
};
}
class AnyClass {
AnyClass({this.one, this.two, this.three});
final String one;
final String two;
final String three;
}
I have read this old thread on GitHub on the same issue. But it seems there was no solution at that time. I also came across reflections in Dart but I am not sure whether they provide a solution in the above described case.
Thanks for any help and advice!

Reflection is provided in the mirrors package, but that package requires the VM and is therefore not supported in Flutter. Have you looked at the reflectable package?
If that doesn't work, a simple Map could do the trick of translating a String in to an object's field, e.g.:
var object = AnyClass();
var field = {'one': object.one, 'two' object.two, 'three': object.three};
print(field['two']); // will print object.two;

You usually use methods like the one below in Dart language to reference a class variable using a String.
Widget _listItemBuilder(
BuildContext context, int index, List<String> listFromDB) {
final labels = Labels.translations['languageCode'];
final String dbUniqueName = listFromDB[index];
print(getByFieldName(dbUniqueName, labels));
return null;
}
String getByFieldName(String name, AnyClass anyClass) {
switch (name) {
case 'one':
return anyClass.one;
case 'one':
return anyClass.one;
case 'one':
return anyClass.one;
default:
throw ('Unsupported');
}
}

Related

What is the efficient way to pass arguments to a Riverpod provider each time it gets initialized in Flutter?

I am currently trying to create an instance of a widget's state (ChangeNotifier) using a global auto-disposable ChangeNotifierProvider. The notifier instance takes in a few arguments to initialize each time the UI is built from scratch.
Let's assume we have the following simple state (or notifier):
class SomeState extends ChangeNotifier {
int _someValue;
SomeState({required int initialValue})
: _someValue = initialValue;
int get someValue => _someValue;
set someValue(int someValue) {
_someValue = someValue;
notifyListeners();
}
}
I used to use the Provider package before switching to Riverpod, where this could've easily been done like so:
class SomeWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
// Passing 2 into state initializer, which may be
// obtained from a different state, but not necessarily.
create: (_) => SomeState(initialValue: 2),
builder: (context, child) => Consumer<SomeState>(
builder: (context, state, child) {
// Will print 2, as it's currently the default value.
return Text('${state.someValue}');
},
),
);
}
}
So with Provider, you can manually call to SomeState constructor with arbitrary arguments when the state is being set up (i.e. provided). However, with Riverpod, it doesn't seem as intuitive to me, mainly because the provider is made to be declared globally:
static final someProvider = ChangeNotifierProvider.autoDispose((ref) => SomeState(2));
Which would end up being used like so:
class SomeWidget extends ConsumerWidget {
#override
Widget build(BuildContext context, WidgetRef ref) {
final state = ref.watch(someProvider);
return Text('${state.someValue}');
}
}
However, with this approach I can't pass parameters like I did in the example using Provider. I also don't want to use the family modifier because I would need to pass the same parameter each time I read/watch the state, even if it's already created.
If it helps, in my current situation I am trying to pass a function (say String Function()? func) into my state on initialization. It's also not feasible to depend on a different provider in this case which would provide such function.
How could I replicate the same functionality in the Provider example, but with Riverpod?
P.S. Apologies if code has syntax errors, as I hand-typed this and don't have an editor with me at the moment. Also, this is my first post so apologies for lack of clarity or format.
Use provider overrides with the param that you need:
First, let's ensure the ProviderScope in the root of the widget-tree.
// Root
ProviderScope(
child: MaterialApp(...)
)
After, create another one in some widget:
Widget build(BuildContext context) {
return ProviderScope(
overrides: [
someProvider.overrideWithProvider(
ChangeNotifierProvider.autoDispose((ref) => SomeState(5)),
),
],
child: Consumer(
builder: (context, ref, child) {
final notifier = ref.watch(someProvider);
final value = notifier.someValue;
return Text('$value'); // shows 5 instead of 2
}
),
);
}
If you do not want to use family then you can put value in another way by combining two providers.
final someValue = StateProvider((ref) => 0);
final someProvider = ChangeNotifierProvider.autoDispose((ref) {
final value = ref.watch(someValue);
return SomeState(value);
});
class SomeState extends ChangeNotifier {
int _someValue;
SomeState(int initialValue) : _someValue = initialValue;
int get someValue => _someValue;
set someValue(int someValue) {
_someValue = someValue;
notifyListeners();
}
}
USAGE:
// From everywhere you can put new value to your ChangeNotifier.
ref.read(someValue.notifier).state++;
But in your case, it's better to use the `family method. It's cleaner and less complicated.

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: Call a method from a class based on properties

The objective is to use the properties of a parent class to call a method and extend the content of that class.
The following code is designed to call a method based on the property of it's parent class. It returns a type error.
It is called like this:
MyToolbar(data: [
{
'MySecondClass': ['red','green','purple']
}
])
And this is the class:
class MyToolbar extends StatelessWidget {
MyToolbar({required this.data})
final List data;
ToolbarContent(type, data) {
if (type == 'MySecondClass') {
return MySecondClass(toggles: data);
}
#override
Widget build(BuildContext context) {
return Stack(children:[
for (List childData in data)
ToolbarContent('mySecondClass', childData),
])}
Firstly this returns the following type error.
_TypeError (type '_InternalLinkedHashMap<String, List<String>>' is not a subtype of type 'List<dynamic>')
Secondly, the list needs to find the key of the property data in order to set the correct function name for the function 'ToolbarContent'.
There's a few issues here. First, as mentioned by temp_, you need to set the List type for data, in this case would be List<Map<String,List<String>>
Second would be that for (List childData in data) needs to be actually for (Map<String,List<String>> childData in data)
The third is an assumption, but I think that there's a typo in your for loop where mySecondClass should be MySecondClass (or the other way)
The correct code would be as follows:
class MyToolbar extends StatelessWidget {
final List<Map<String, List<String>>> data;
MyToolbar({required this.data});
#override
Widget build(BuildContext context) {
var children = <Widget>[];
data.forEach((childData) {
childData.forEach((key, stringList) {
//I'm assuming Toolbar content takes the key of the map i.e. MySecondClass
//as the first param and the List for the key as the second param
children.add(ToolbarContent(key, stringList));
});
});
return Stack(
children: children,
);
}
}
Note: I'm also assuming that the ToolbarContent is another class, but do let me know if otherwise.
By default Dart sets any List to List<dynamic>. This is what the error is saying. You need to cast your List, try this instead final List<Map<String, List<String>> data;

Retrieving the provider value gives null as its value

I have created a provider to pass down the values within the WidgetTree, but when I try to retrieve the value, it gives me null as its value.
Initializing the value for the Provider:
GetCurrentCourse course = GetCurrentCourse();
course.currentCourse(
courseID: courseID,
courseDocID: courseDocID,
);
Below is the code related to the Provider:
import 'package:flutter/material.dart';
class GetCurrentCourse extends ChangeNotifier {
String? currentCourseID;
String? currentCourseDocID;
void currentCourse({courseID, courseDocID}) {
this.currentCourseDocID = courseDocID;
this.currentCourseID = courseID;
print("Current Course ID is ${this.currentCourseID}");
print("Current Course Doc ID is ${this.currentCourseDocID}");
notifyListeners();
}
}
When I print the values within the above GetCurrentCourse class. I do see the correct values getting printed.
Below is the code showcasing the Provider defined within the main.dart file as shown below:
ChangeNotifierProvider<GetCurrentCourse>(
create: (_) => GetCurrentCourse(),
),
Below is the code where I'm trying to consume the GetCurrentCourse Provider:
class CoursePageBody extends StatelessWidget {
#override
Widget build(BuildContext context) {
print("Printing Value of Course from Provider");
print(Provider.of<GetCurrentCourse>(context).currentCourseDocID);
.
.
}
}
Now, when I try to access the value of the Provider and try to print it. It prints null.
What can I try to fix it?
Your initialization code should be:
var courseProvider = Provider.of<GetCurrentCourse>(context, listen: false);
courseProvider.currentCourse(
courseID: courseID,
courseDocID: courseDocID,
);
And the widget that is going to access it should be wrapped in a Consumer that would look something like:
class CoursePageBody extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Consumer<GetCurrentCourse>(
builder: (context, courseProvider, child) {
print("Printing Value of Course from Provider");
print(courseProvider.currentCourseDocID);
// rest of the code
}
);
}
}

Flutter - How to parse array items in custom widget?

I want to map my data in a custom widget, but I am not sure how to parse them in that widget.
Here is a type of data:
Here is Widget who need to serve that data:
The problem is how to prepare a custom widget in the constructor class? And how to display data in a tree? e.g this.module['title], or object notation this.module.title :)
Help!
I am a newbie in Flutter.
Thanks!
First create a class to handle your data.
class Module {
String title;
int id;
String excerpt; // I'm not sure about types... Since i can't see the values
String thumbnail;
String content;
Module.fromJson(data){
this.title = data["title"];
this.id = data["id"];
this.excerpt = data["excerpt"];
this.thumbnail = data["thumbnail"];
this.content = data["content"];
}
}
Then you use it where you fetch your data (obviously in onInit()).
List<Module> modules = List.empty();
yourMethode(){
YourApi.route().then((result){
setState((){
modules = result.map((module){return Module.fromJson(module);});
});
});
}
}
Then in your custom widget
class ModuleList extends StatelessWidget{
final List<Module> modules;
/// The constructor
const ModuleList(this.modules);
#override
Widget build(BuildContext context) {
return ListView.builder(itemBuilder: (BuildContext context, int index) {
Module myModule = modules[index];
return Column(
children: [
Text(myModule.title)
// other elements here
],
);
});
}
}
Finally use the widget in the same widget you made your API cals
//...
child: ModuleList(modules)
//...