Flutter: Call a method from a class based on properties - flutter

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;

Related

How to create a local Object instance from the generic type

I wanna know how can I create an instance of an Object from the passed generic Type T passed from the super class constructor
this example of what I want to achieve but it's wrong :
class Widget<T> extends StatelessWidget {
Widget({super.key});
final T instanceOfT = T(); // throws 'T' isn't a function.
#override
Widget build(BuildContext context) {
return Text(instanceOfT.exampleStringProperty);
}
}
class ExampleClass {
final String exampleStringProperty;
const ExampleClass({this.exampleStringProperty = "exampleValue"});
}
I'm expecting that I should pass the Type from the generic call when calling the Widget constructor like this:
Widget<ExampleClass>(),
Then an instance of the ExampleClass should be created and works fine.
Any ideas on this ?

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();
}
}),
),
);
}
}

Generic Class Return by a provider?

I am sure there is a way to return a dynamic or generic instance of a class from a riverpod FutureProvider?
I can fetch different tracker data from an API. It can be for Physical Activity or Weight data for instance. So far, I tried this but does not work, not even compile:
final trackerDataProvider = FutureProvider.family<AbstractTrackerData, TrackerType>((ref, trackerType) async {
final repository = ref.read(trackerRepositoryProvider);
AbstractTrackerData trackerData =
await repository.getTrackerData(trackerType);
return trackerData;
});
and in my Widget
class PhysicalActivityLandingTrackerScreen extends HookWidget {
#override
Widget build(BuildContext context) {
final trackerData = useProvider(trackerDataProvider(TrackerType.physicalactivity));
...
}
}
and my tracker data classes
class SmokeFree extends AbstractTrackerData {
...
}
class PhysicalActivity extends AbstractTrackerData {
...
}
class Weight extends AbstractTrackerData {
...
}
and the enum
enum TrackerType {
physicalactivity,
exercise,
weeklyweight,
bloodpressure,
saltyfoodsubstitution,
goodfatsubstitution,
medicationuse,
smokefree
}
Yes, because Generic methods have a type parameter (the diamond operator enclosing the type) before the return type of the method declaration. ... Generic methods can have different type parameters separated by commas in the method signature. Method body for a generic method is just like a normal method.
Hence we can say that it is returned by provider.

Flutter: Convert String to class field name during runtime

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');
}
}

The instance member 'params' can't be accessed in an initializer

class LevelUp extends GetxController {
Map<String, String> params = Get.arguments;
var myTest = params.[comLevel];
}
Error report--"The instance member 'params' can't be accessed in an initializer." I am new to programming and this is being called directly from a widget. I checked the LevelUp map and it has contents. The error occurs where I am trying to assign the param value to myTest. It doesn't matter if I put the key in quotes or provide an integer. Any advice would be greatly appreciated.
You can't access params before you've initialized the object. To fix your example, move your myTest initialization into a constructor.
Also, I don't believe you should have a period before [comLevel].
class LevelUp extends GetxController {
Map<String, String> params = Get.arguments;
String myTest;
LevelUp() {
myTest = params[comLevel];
}
}
Null safety update:
Use late keyword: Dart 2.12 comes with late keyword which helps you do the lazy initialization which means until the field bar is used it would remain uninitialized.
class Test {
int foo = 0;
late int bar = foo; // No error
}
Although this question has been answered for the OP's case, I want to offer a solution to those receiving this error in a StatefulWidget scenario.
Consider a situation where you would want to have a list of selectable items that dictate which category to display. In this case, the constructor might look something like this:
CategoryScrollView({
this.categories,
this.defaultSelection = 0,
});
final List<String> categories;
final int defaultSelection;
Note the property defaultSelection is responsible for specifying which category should be selected by default. You would probably also want to keep track of which category is selected after initialization, so I will create selectedCategory. I want to assign selectedCategory to defaultSelection so that the default selection takes effect. In _CategoryScrollViewState, you cannot do the following:
class _CategoryScrollViewState extends State<CategoryScrollView> {
int selectedCategory = widget.defaultSelection; // ERROR
#override
Widget build(BuildContext context) {
...
}
}
The line, int selectedCategory = widget.defaultSelection; will not work because defaultSelection it hasn't been initialized yet (mentioned in other answer). Therefore, the error is raised:
The instance member 'widget' can't be accessed in an initializer.
The solution is to assign selectedCategory to defaultSelection inside of the initState method, initializing it outside of the method:
class _CategoryScrollView extends State<CategoryScrollView> {
int selectedCategory;
void initState() {
selectedCategory = widget.defaultSelection;
super.initState();
}
A simple example, where it shows how we can resolve the above issue,
Example: Create an instance of class B, and pass an instance of class A in the parameter of it
WRONG(Compile time error of initializer):
final A _a = A();
final B _b = B(_a);
shows error: The instance member '_a' can't be accessed in an initializer.
Right:
final A _a = A();
late final B _b;
AppointmentRepository() {
_b = B(_a);
}
#100% working solution
:
Juts place var myTest = params.[comLevel];
below your Build method.
eg.
class LevelUp extends GetxController {
Map<String, String> params = Get.arguments;
Widget build(BuildContext context) {
var myTest = params.[comLevel];
}
}
For me it happened Because i was trying to access a Property of a class instance (Lets Say Class A ) And Use this property to initialize Another Class (Class B) , The Property Was Integer Number and Was Defined
However , Since i didn't Make an Object from "Class A" I can access those propertied Belong to it !
I tried to use this property inside the "Build" Method so that an object is "Created/Built" And it Worked !
I also got the similar error.
And I found the solution as follows.
My first code:
final BuildContext mycontext = GlobalContextClass.navigatorKey.currentContext;
final PsValueHolder psValueHolder = Provider.of<PsValueHolder>(mycontext, listen: false);
Next is the code where the error is fixed:
final PsValueHolder psValueHolder = Provider.of<PsValueHolder>(GlobalContextClass.navigatorKey.currentContext, listen: false);
Instead of defining 2 variables in a row, I placed the first variable directly in the place of the 2nd variable.
Another solution is making your variable, a GetX parameter.
int count_myProducts = cartItems.length; //The instance member 'cartItems' can't be accessed in an initializer. (Documentation)
int get count_myProducts => cartItems.length;
see this video at 27:34
GetX State Management tutorial with Flutter
https://www.youtube.com/watch?v=ZnevdXDH25Q&ab_channel=CodeX
Just carry
var myTest = params.[comLevel];
into Widget build{} below.
like this :
class LevelUp extends GetxController {
Map<String, String> params = Get.arguments;
Widget build(BuildContext context) {
var myTest = params.[comLevel];
}
}