Extension of abstract class doesn't work properly in Dart - flutter

I have faced with strange behavior of extensions of abstract class in the Dart.
The Code
I have following files:
BaseListItem.dart
abstract class BaseListItem {
ImageProvider get _icon;
IHealthChecker get _healthChecker;
String get _name;
String get _unit;
Widget build(BuildContext context) {
... build widgets tree ...
}
}
Temperature.dart
class Temperature extends BaseListItem {
#override
IHealthChecker get _healthChecker => TemperatureHealthChecker();
#override
ImageProvider<Object> get _icon => const Svg('assets/images/icons/Temperature.svg');
#override
String get _name => "Temperature";
#override
String get _unit => "°C";
}
And all this inheritance stuff I am using in SensorsList.dart file.
class SensorsList extends StatefulWidget {
#override
State<StatefulWidget> createState() => _StateSensorsList();
}
class _StateSensorsList extends State<SensorsList> {
final List<BaseListItem> sensors = [Temperature(), Humidity()];
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: sensors.length,
shrinkWrap: true,
itemBuilder: (BuildContext context, int index) {
return sensors[index].build(context);
});
}
}
The problem
This code built but fail at run time with next exception
Exception has occurred.
NoSuchMethodError (NoSuchMethodError: Class 'Temperature' has no instance getter '_icon'.
Receiver: Instance of 'Temperature'
Tried calling: _icon)
❗️The problem disappears if I put abstract class BaseListItem and his extension Temperature into a single file.❗️

The reason why it happens is because the variables are private. Private variables can only be accessed within the same file. See also In Dart, how to access a parent private var from a subclass in another file?

Related

How to start Flutter app with loading, using state pattern with provider

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

Access state from ui without an if statement

am following this Bloc's official example and I couldn't find a way how to access the state without that if statement.
Let's have the example below, I would like to display a specific text based on the initial value of showText, the only possible solution to access the state is via:
if(statement is ExampleInitial) {state.showText? return Text("yes") : return Text("no")}
But am finding this solution hard to implement when you have more values with initial values. Or am I doing this wrong?
////////// bloc
class ExampleBloc extends Bloc<ExampleEvent, ExampleState> {
ExampleBloc() : super(const ExampleInitial()) {
on<ExampleStarted>(_onExampleStarted);
}
void _onExampleStarted(ExampleStarted event, Emitter<ExampleState> emit) {
emit(const ExampleInitial());
}
}
////////// event
part of 'example_bloc.dart';
abstract class ExampleEvent extends Equatable {
const ExampleEvent();
}
class ExampleStarted extends ExampleEvent {
#override
List<Object> get props => [];
}
////////// state
part of 'example_bloc.dart';
abstract class ExampleState extends Equatable {
const ExampleState();
}
////////// state
class ExampleInitial extends ExampleState {
final bool showText = false;
const ExampleInitial();
#override
List<Object> get props => [showText];
}
// ui
class CreateExampleScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocBuilder<ExampleBloc, ExampleState>(
builder: (context, state) {
return state.showText ? Text("yes") :Text("no"); // can't access to state.showText
});
}
}
You can declare a variable inside Bloc Class which will be global and need to be set inside the 'bloc.dart' file like in the case of Provider Package. This variable does not need state to be checked before accessing it in UI. You can access this value from the Navigation tree using context.
////////// bloc
class ExampleBloc extends Bloc<ExampleEvent, ExampleState> {
ExampleBloc() : super(const ExampleInitial()) {
on<ExampleStarted>(_onExampleStarted);
}
bool showText = false;
void _onExampleStarted(ExampleStarted event, Emitter<ExampleState> emit) {
emit(const ExampleInitial());
}
}
// ui
class CreateExampleScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocProvider.of<ExampleBloc>(context).showText
? const Text('Yes')
: const Text('No');
}
}
There is another way in which you declare abstract State Class to always have the boolean value. So, whatever new class extends those State will have inherited boolean value from parent class. This concept is called inheritance in OOP.
////////// state
abstract class ExampleState extends Equatable {
const ExampleState();
final bool showText = false;
}
////////// state
class ExampleInitial extends ExampleState {
const ExampleInitial();
// You can also set ExampleInitial to accept showText and send it to its
// parent class using 'super' method in constructor,
// if parent class has constructor with 'showText' as boolean
#override
List<Object> get props => [];
}
// ui
class CreateExampleScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocBuilder<ExampleBloc, ExampleState>(builder: (context, state) {
return state.showText ? const Text("yes") : const Text("no");
});
}
}
A pragmatic usecase for different State Classes having different state variables is as follows:
Let's account for three states while fetching data from api
-if(state is DataLoadingState),
// there is no need for state
-if(state is DataLoadedState)
// state need to have a variable named weatherData containing temperatures, cities and so on.
-if(state is ErrorWhileLoadingState)
// state needs to have a reason for the error. For example: errorMsg: 'Internal Server Error'
So, you need to check the state before accessing its values.

Error: Type argument 'T' doesn't conform to the bound 'Object' of the type variable 'T' on 'GetIt.call'. After migrating to Null Safety

I'm in the process of migrating over a large project to null safety and I'm coming across a strange error I'm not entirely sure how to fix.
"Error: Type argument 'T' doesn't conform to the bound 'Object' of the type variable 'T' on 'GetIt.call'."
class BaseView<T extends BaseProvider?> extends StatefulWidget {
final Widget Function(BuildContext context, T value, Widget? child)? builder;
final Function(T)? onModelReady;
BaseView({this.builder, this.onModelReady});
#override
_BaseViewState<T> createState() => _BaseViewState<T>();
}
class _BaseViewState<T extends BaseProvider?> extends State<BaseView<T?>> {
T model = locator<T>(); <---- This is throwing it
#override
void initState() {
if (widget.onModelReady != null) {
widget.onModelReady!(model);
}
super.initState();
}
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider<T?>(
create: (context) => model,
child: Consumer<T>(builder: widget.builder!),
);
}
}
I can't find much info on this error and so far any method I've tried hasn't worked out. Can anyone be of assistance?
I'm using Provider for state management and BaseView is what wraps all my other views during build; e.g.:
class EquipmentMainView extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BaseView<EquipmentProvider>(
onModelReady: (model) async {
model.getAllFunctions();
},..
Posting here for anyone else that might eventually run across this in the future, just changed the nullability of BaseProvider suggested by jamesdlin by changing
class BaseView<T extends BaseProvider?>
to
class BaseView<T extends BaseProvider>
I had similar issues when I upgraded to flutter 2.0 I made the generic methods in Generic class to explicitly extends Base class Object i.e
from:
import 'package:get_it/get_it.dart';
class PoultryBabaRegistry<T> {
static GetIt _getIt = GetIt.instance;
static void register<T>(T model) {
_getIt.registerSingleton<T extends Object >(model, signalsReady: true);
}
static void remove<T>(T model) {
_getIt.unregister<T extends Object>(instance:model);
}
static T getIt<T>() {
return _getIt.get<T>();
}
}
to:
class PoultryBabaRegistry<T extends Object> {
static GetIt _getIt = GetIt.instance;
static void register<T extends Object>(T model) {
_getIt.registerSingleton<T >(model, signalsReady: true);
}
static void remove<T extends Object>(T model) {
_getIt.unregister<T>(instance:model);
}
static T getIt<T extends Object>() {
return _getIt.get<T>();
}
}

Flutter, how to pass a parameter to constructor

this is my code, i dont know how to pass this listOfTiles to my StatefulWidget, can u help me and describe how it works?
body: new ListView.builder(
itemBuilder: (BuildContext context, int index) {
return new StuffInTilesState(listOfTiles[index]);//i want to pass this
},
itemCount: listOfTiles.length,
),
),
);
}
}
class StuffInTiles extends StatefulWidget{
#override
StuffInTilesState createState() => StuffInTilesState();//i know i need to change this, but i dont know how
}
class StuffInTilesState extends State<StuffInTiles> {
final MyTile myTile;
StuffInTilesState(this.myTile);//this is constructor, shuld this also be changed?
final _controller = TextEditingController();
String name = "";
If u want to see my working code: https://pastebin.pl/view/c4dbc2af If u want to see my not working code: https://pastebin.pl/view/83f9cad0 (https://codeshare.io/GLLm66)
You need to use the widget class constructor and not the state class.
You can access the values ​​in the state class with widget.YouProperty
class StuffInTiles extends StatefulWidget {
final MyTile myTile;
const StuffInTiles(this.myTile);
#override
_StuffInTilesState createState() => _StuffInTilesState();
}
class _StuffInTilesState extends State<StuffInTiles> {
#override
Widget build(BuildContext context) {
return Container(child:
Text(widget.myTile),);
}
}

use passed data before build method flutter

I'm trying to access the information I've passed over from a previous class before the build method begins. But it's saying only static members can be accessed in initializers. I don't really want to use the static property, partially because I wouldn't know how to use it, but also because I think it seems unnecessary. In previous pages I've been able to access the data but only after the build method, does anyone know how I can access it before? Thanks all
class FirstPage extends StatefulWidget {
#override
_FirstPageState createState() => _FirstPageState();
}
class _FirstPageState extends State<FirstPage> {
List<MyProvider> myList;
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView.builder(
itemCount: myList.length,
itemBuilder: (context, index) {
String imgPath = myList[index].image;
String myTextPath = myList[index].name;
String locationNamePath = myList[index].location;
double distancePath = myList[index].distance;
String myName = '${myTextPath} ''${locationNamePath}';
return MyCard(
locationText: locationNamePath,
myText: myTextPath,
assetImage: Image.network(imgPath),
function: (){
Provider.of<Data>(context, listen: false).addLogo(Image.network(imgPath));
Navigator.push(context, MaterialPageRoute(builder: (context) => SecondPage(myName: myName,)));
},
);
}),
);
}
}
My next page accesses the data using a key but it seems not to be able to use it before the build method, and that's what I need to get around!
class SecondPage extends StatefulWidget {
final String myName;
const SecondPage({Key key, this.myName})
: super(key: key);
#override
_SecondPageState createState() => _SecondPageState();
}
class _SecondPageState extends State<SecondPage> with TickerProviderStateMixin {
final CollectionReference myItemsReference = Firestore.instance.collection('${widget.myName}');
// This is where the error is
#override
Widget build(BuildContext context) {
return Scaffold();
}
}
Use the initState method for anything related to initialization of State. See this for more on initState.
Example:
CollectionReference myItemsReference;
#override
void initState() {
myItemsReference = Firestore.instance.collection('${widget.myName}');
}