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)
//...
Related
I'm building my first app, and for state management I'm using ValueChangeNotifier and Provider with the state pattern. But when I start my app, I get the following error:
Exception has occurred.
FlutterError (setState() or markNeedsBuild() called during build.
This _InheritedProviderScope<EvaluationStore?> widget cannot be marked as needing to build because the framework is already in the process of building widgets. A widget can be marked as needing to be built during the build phase only if one of its ancestors is currently building. This exception is allowed because the framework builds parent widgets before children, which means a dirty descendant will always be built. Otherwise, the framework might not visit this widget during this build phase.
The widget on which setState() or markNeedsBuild() was called was:
_InheritedProviderScope<EvaluationStore?>
The widget which was currently being built when the offending call was made was:
Builder)
I don't know how to show my problem without showing my project's classes, so I apologize if this gets too long.
I created a model class.
class EvaluationModel {
final String token;
final DateTime creation;
final String technicians;
final String customer;
final String responsible;
final String compressor;
final int horimeter;
final int oilType;
final int oil;
final int oilFilter;
final int airFilter;
final int separatorFilter;
final int revitalize;
final int revitalization;
final String? technicalAdvice;
final bool uploaded;
// continues with the basic methods of a data class...
}
So I created a service class that is responsible for the EvaluationModel methods, where I created a method to fill my list with data coming from a MySQL database.
class EvaluationService {
Future<List<EvaluationModel>> fetchEvaluations(
String creationStart,
String creationEnd,
String technicians,
String customer,
String compressor) async {
List<EvaluationModel> evaluations = <EvaluationModel>[];
EvaluationModel evaluation;
final MySqlConnection conn = await Database.getDbConnection();
final Results result = await conn.query(
await rootBundle.loadString('lib/assets/evaluation_select.sql'),
[creationStart, creationEnd, technicians, customer, compressor]);
await conn.close();
for (var row in result) {
evaluation = EvaluationModel(
token: row['token'],
creation: row['creation'],
technicians: row['technicians'],
customer: row['customer'],
responsible: row['responsible'],
compressor: row['compressor'],
horimeter: row['horimeter'],
oilType: row['oiltype'],
oil: row['oil'],
oilFilter: row['oilfilter'],
airFilter: row['airfilter'],
separatorFilter: row['separatorfilter'],
revitalize: row['revitalize'],
revitalization: row['revitalization'],
technicalAdvice: row['technicalAdvice'],
uploaded: true);
evaluations.add(evaluation);
}
return evaluations;
}
}
Then I created the EvaluationState and EvaluationStore class to manage the state of my page.
abstract class EvaluationState {}
class InitialEvaluationState extends EvaluationState {}
class LoadingEvaluationState extends EvaluationState {}
class SuccessEvaluationState extends EvaluationState {
final List<EvaluationModel> evaluations;
SuccessEvaluationState(this.evaluations);
}
class ErrorEvaluationState extends EvaluationState {
final String message;
ErrorEvaluationState(this.message);
}
class EvaluationStore extends ValueNotifier<EvaluationState> {
final EvaluationService service;
EvaluationStore(this.service) : super(InitialEvaluationState());
Future fetchEvaluations(String creationStart, String creationEnd,
String technicians, String customer, String compressor) async {
value = LoadingEvaluationState();
try {
final evaluations = await service.fetchEvaluations(
creationStart, creationEnd, technicians, customer, compressor);
value = SuccessEvaluationState(evaluations);
} catch (e) {
value = ErrorEvaluationState(e.toString());
}
}
}
So, to work with the Provider I did it like this in the MyApp class.
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
Provider(create: (_) => EvaluationService()),
ChangeNotifierProvider(
create: (context) => EvaluationStore(context.read()))
],
child: MaterialApp(
title: 'Avaliação',
theme: ThemeData(
primarySwatch: Colors.deepOrange,
),
home: const EvaluationsPage(),
),
);
}
And finally, on the page I'm treating it like this:
class EvaluationsPage extends StatefulWidget {
const EvaluationsPage({Key? key}) : super(key: key);
#override
State<EvaluationsPage> createState() => _EvaluationsPageState();
}
class _EvaluationsPageState extends State<EvaluationsPage> {
#override
void initState() {
super.initState();
context
.read<EvaluationStore>()
.fetchEvaluations('0001-01-01', '9999-12-31', '%', '%', '%');
}
#override
Widget build(BuildContext context) {
final store = context.watch<EvaluationStore>();
final state = store.value;
Widget? child;
if (state is LoadingEvaluationState) {
child = const Center(child: CircularProgressIndicator());
}
if (state is ErrorEvaluationState) {
child = Center(child: Text(state.message));
}
if (state is SuccessEvaluationState) {
child = ListView.builder(
itemCount: state.evaluations.length,
itemBuilder: (context, index) {
return ListTile(title: Text(state.evaluations[index].customer));
});
}
return Scaffold(
appBar: AppBar(title: const Text('Avaliações')),
body: child ?? Container(),
);
}
}
Note: If I remove the line "value = LoadingEvaluationState();" from the Evaluation Store class, the app runs normally.
If anyone can help me, I can even make the project available.
I'm a beginner, I'm totally stuck, I don't know what to try.
the error occured because while execute the initState method, you call rebuild .
simple solution:
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_){
context
.read<EvaluationStore>()
.fetchEvaluations('0001-01-01', '9999-12-31', '%', '%', '%');
}
});
I'm trying to change the variable from another stateful class.
class first extends statefulwidget {
bool text = false;
Widget build(BuildContext context) {
setState((){});
return Container(
child: text ? Text('Hello') : Text('check')
);
}
}
class second extends statefulwidget {
Widget build(BuildContext context) {
return Container(
child: IconButton(
onPressed: () {
first fir = first();
setState((){
fir.test = true;
});
}
)
);
}
}
widget shows only check not showing Hello
This is my code...Ignore spelling mistakes and camelcase
Give me the solutions if you know..
If you are trying to access data on multiple screens, the Provider package could help you. It stores global data accessible from all classes, without the need of creating constructors. It's good for big apps.
Here are some steps to use it (there is also a lot of info online):
Import provider in pubspec.yaml
Create your provider.dart file. For example:
class HeroInfo with ChangeNotifier{
String _hero = 'Ironman'
get hero {
return _hero;
}
set hero (String heroName) {
_hero = heroName;
notifyListeners();
}
}
Wrap your MaterialApp (probably on main.dart) with ChangeNotifierProvider.
return ChangeNotifierProvider(
builder: (context) => HeroInfo(),
child: MaterialApp(...),
);
Use it on your application! Call the provider inside any build method and get data:
#override
Widget build(BuildContext context){
final heroProvider = Provider.of<HeroInfo>(context);
return Column {
children: [
Text(heroProvider.hero)
]
}
}
Or set data:
heroProvider.hero = 'Superman';
try to reference to this answer, create function to set boolean in class1 and pass as parameter to class 2 and execute it :
typedef void MyCallback(int foo);
class MyClass {
void doSomething(int i){
}
MyOtherClass myOtherClass = new MyOtherClass(doSomething);
}
class MyOtherClass {
final MyCallback callback;
MyOtherClass(this.callback);
}
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');
}
}
I've been using flutter_redux for only very few days and I'm wondering what's the difference between:
class BtnCustom extends StatelessWidget {
#override
Widget build(BuildContext context) {
final store = StoreProvider.of<AppState>(context);
return FlatButton(
onPressed: store.dispatch(MyCustomAction),
child: Text(store.state.MyCustomTxt),
);
}
}
and
class BtnCustom extends StatelessWidget {
#override
Widget build(BuildContext context) {
return StoreConnector<AppState, _ViewModel>(
converter: (store) => _ViewModel(
txt: store.state.MyCustomTxt,
onPressed: store.dispatch(MyCustomAction)),
builder: (BuildContext context, _ViewModel vm) {
return FlatButton(
onPressed: vm.onPressed,
child: Text(vm.txt),
);
},
);
}
}
class _ViewModel {
final String txt;
final void Function() onPressed;
_ViewModel({this.txt, this.onPressed});
}
?
The first one seems so handy to use. Are they any advantages or drawbacks of using one over another I should be aware of?
According to the documentation, the StoreConnector will rebuild the widget in it so:
Is it ok to not use a StoreConnector when you don't need to rebuild a widget?
Is it ok to have multiple widgets within a StoreConnector?
StoreConnector gives you more control over widget, especially when you don't want to rebuild it. StoreConnector:
lets you detect whether the ViewModel has changed (whether widget should be rebuilt) if you use distinct: true and override hashCode and == in your ViewModel;
lets you skip widget rebuild altogether by quickly checking some specific store.state:
StoreConnector<AppState, MyViewModel>(
distinct: true,
ignoreChange: (state) {
return state.someVariable == theValueIDontCareAbout;
},
...
),
class MyViewModel{
#override
bool operator ==(other) {
return (other is MyViewModel) && (this.someVmVariable == other.someVmVariable);
}
#override
int get hashCode {
int result = 17;
result = 37 * result + someVmVariable.hashCode;
return result;
}
}
And some more fine-grained controls. Take a look at the documentation of StoreConnector's constructor. If multiple widgets inside store sonnector share the same ViewModel it is natural to have them like that. However, if it's possible to prevent them from rebulding by separating their ViewModels you can use separate StoreConnectors.
I have one screen and I want to pass data from that screen to the drawer. My drawer class looks like this:
class InitDrawer extends StatelessWidget {
final Auth auth;
InitDrawer({this.auth});
#override
Widget build(BuildContext context) {
final String _name = auth.name;
final String _email = auth.email;
final drawerHeader = UserAccountsDrawerHeader(
accountName: Text(_name),
accountEmail: Text(_email),
currentAccountPicture: CircleAvatar(
...
),
);
return ListView(
children: <Widget>[
...
],
);
}
}
This is my screen class which is passing the data
class QRScannerScreen extends StatefulWidget {
static const routeName = '/qr';
final Auth auth;
const QRScannerScreen(this.auth);
#override
_QRScannerScreenState createState() => _QRScannerScreenState();
}
class _QRScannerScreenState extends State<QRScannerScreen> {
final auth = widget.auth;
...
drawer: InitDrawer(auth: auth,),
...
}
And finally this is my Auth class/notified listener
class Auth with ChangeNotifier {
String _token;
DateTime _expiryDate;
String _userId;
int _carId;
String _email;
String _name;
String get name {
return _name;
}
String get email {
return _email;
}
...
}
I think the whole problem comes from that the auth is null and when I try to view the drawer I am getting an error saying that my widget of type Text can not contain a text which is null. Because I am getting the auth with await and async maybe the data it is not getting there in time when screen loads and it is resulting in null. Maybe I have to use setState, but I am new to Flutter/Dart and I don't know where to use it.
Try to remove final auth = widget.auth; and use widget.auth inside build method, or at least define final auth in initState method. But if you decide to use initState approach, keep in mind that you might need to update auth value inside your state class in didUpdateWidget