how to refresh the display of a DragTarget Widget - dart

Initially my 'TRY' pockets are empty and display thus:
Empty pockets
The pockets are then filled by the user dragging counters into them. Once all the pockets are filled e.g.
Filled pockets
they are then processed and the pockets can then be emptied and should be redisplayed. And that is the problem: the counters are still showing.
Here is the code for the DragTarget:
/// Drag Target --------
class CatcherPocket extends StatefulWidget {
final int position;
int colourID;
Color colour;
CatcherPocket(
{Key? key,
required this.position,
required this.colourID,
required this.colour})
: super(key: key);
#override
State<CatcherPocket> createState() {
// ignore: no_logic_in_create_state
return CatcherPocketState(
position: position, colourID: colourID, colour: colour);
}
}
class CatcherPocketState extends State<CatcherPocket> {
final int position;
int colourID;
Color colour;
CatcherPocketState(
{required this.position, required this.colourID, required this.colour});
#override
DragTarget build(BuildContext context) {
return DragTarget<CounterItem>(
builder: (context, candidateItems, rejectedItems) {
return Pocket(colourID: colourID, colour: colour);
},
onAccept: (ci) {
setState(() {
colourID = ci.uid;
colour = ci.imageProvider.colour;
});
GameState.updateTry(position, colourID, colour);
},
);
}
}
// ---------------- END OF Draggable counter stuff ------------------
I populate a list of pocket handles when they are initially built and use these to reset their internal state using the following code:
static List<CatcherPocket> cps = []; // to be populated on initilization
static resetTryCounters() {
for (CatcherPocket cp in cps) {
cp.colour = DDDtheme.standard.primaryColor;
cp.colourID = -1;
}
}
After invoking a 'setState' in the root ancestor Widget I would expect it to redraw the pockets in this reset state but it doesn't.
Any ideas why and a fix would be greatly appreciated

After some days of not thinking about it, the solution came to me. But it is crude and I am sure a more elegant solution is possible. Basically it involves changing the state of the CatcherPockets externally.
I modified the CatcherPocket to keep a handle (the 'cps' variable) to its state:
class CatcherPocket extends StatefulWidget {
final int position;
int colourID;
Color colour;
late CatcherPocketState cps = CatcherPocketState(
position: position, colourID: colourID, colour: colour);
CatcherPocket(
{Key? key,
required this.position,
required this.colourID,
required this.colour})
: super(key: key);
#override
State<CatcherPocket> createState() {
// ignore: no_logic_in_create_state
return cps;
}
}
This enabled a modified 'resetTryCounters' to get at the CatcherPocketState thus:
static List<CatcherPocket> cpockets = [];
static resetTryCounters() {
for (CatcherPocket cp in cpockets) {
cp.colour = DDDtheme.standard.primaryColor;
cp.colourID = -1;
if (cp.cps.mounted) {
cp.cps.colour = DDDtheme.standard.primaryColor;
cp.cps.colourID = -1;
cp.cps.setState(() {});
}
}
}
It works, but it blows the idea of encapsulation out of the water. Also, I wonder if there is a better way of passing the initialization parms for CatcherPocket to its State.

Related

flutter riverpod question: watch a provider from another provider and trigger action on the first provider

I am trying to figure out how can i watch a StateNotifierProvider and trigger some methods (defined in the class subclassing StateNotifier) on this provider after having done some async computations in another Provider watching the StateNotifierProvider.
Loking at the example below
i need to perform a reset from the RandomAdderNotifierobject provided by the randomAdderProvider if the doneProvider return true.
I try to reset from the doReset Provider. However the provider has nothing to provide.
The point is that both the doneProvider and the doreset provider are not rebuild on state changes of AdderProvider.
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:equatable/equatable.dart';
void main() {
runApp(
const ProviderScope(child: MyApp()),
);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(home: Home());
}
}
final randomProvider = Provider<Random>((ref) {
return Random(1234);
});
//immutable state
class RandomAdder extends Equatable {
final int sum;
const RandomAdder(this.sum);
#override
List<Object> get props => [sum];
}
//State notifier extension
class RandomAdderNotifier extends StateNotifier<RandomAdder> {
RandomAdderNotifier(this.ref) : super(const RandomAdder(0));
final Ref ref;
void randomIncrement() {
state = RandomAdder(state.sum + ref.read(randomProvider).nextInt(5));
}
void reset() {
state = RandomAdder(0);
}
}
/// Providers are declared globally and specify how to create a state
final randomAdderProvider =
StateNotifierProvider<RandomAdderNotifier, RandomAdder>(
(ref) {
return RandomAdderNotifier(ref);
},
);
Future<bool> delayedRandomDecision(ref) async {
int delay = ref.read(randomProvider).nextInt(5);
await Future.delayed(Duration(seconds: delay));
print("You waited $delay seconds for a decision.");
return delay > 4;
}
final doneProvider = FutureProvider<bool>(
(ref) async {
ref.watch(randomAdderProvider);
bool decision = await delayedRandomDecision(ref);
print("the decision is $decision");
return decision;
},
);
final doreset = Provider((ref) {
if (ref.watch(doneProvider).value!) {
ref.read(randomAdderProvider.notifier).reset();
}
});
class Home extends ConsumerWidget {
#override
Widget build(BuildContext context, WidgetRef ref) {
return Scaffold(
appBar: AppBar(title: const Text('Counter example')),
body: Center(
// Consumer is a widget that allows you reading providers.
child: Consumer(builder: (context, ref, _) {
final count = ref.watch(randomAdderProvider);
return Text('$count');
}),
),
floatingActionButton: FloatingActionButton(
// The read method is a utility to read a provider without listening to it
onPressed: () =>
ref.read(randomAdderProvider.notifier).randomIncrement(),
child: const Icon(Icons.add),
),
);
}
}
I think ref.listen is more appropriate for usage within the doreset function than ref.watch.
Similarly to ref.watch, it is possible to use ref.listen to observe a provider.
The main difference between them is that, rather than rebuilding the widget/provider if the listened to provider changes, using ref.listen will instead call a custom function.
As per the Riverpod documentation
For ref.listen we need an additional argument - the callback function that we wish to execute on state changes - Source
The ref.listen method needs 2 positional arguments, the first one is the Provider and the second one is the callback function that we want to execute when the state changes.
The callback function when called will be passed 2 values, the value of the previous State and the value of the new State.
&
We will need to handle an AsyncValue - Source
As you can see, listening to a FutureProvider inside a widget returns an AsyncValue – which allows handling the error/loading states.
In Practice
doreset function
I chose to handle the AsyncValue by only handling the data case with state.whenData()
final doReset = Provider<void>(
(ref) {
final done = ref.listen<AsyncValue<bool>>(doneProvider, (previousState, state) {
state.whenData((value) {
if (value) {ref.read(randomAdderProvider.notifier).reset();}
});
});
},
);
Don't forget to watch either doReset/doneProvider in your Home Widget's build method. Without that neither will kick off (Don't have an explanation for this behaviour)
class Home extends ConsumerWidget {
#override
Widget build(BuildContext context, WidgetRef ref) {
ref.watch(doReset);
...
Lastly, your random function will never meet the condition for true that you have setup as delay>4, as the max possible delay is 4. Try instead using delay>3 or delay=4.
Also perhaps disable the button to prevent clicks while awaiting updates
and in a case where you are using ChangeNotifier You can pass ref in you provider and use the ref same as we can use in ConsumerWidget.
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class YourProvider extends ChangeNotifier {
Ref ref;
YourProvider(this.ref) : super();
callOtherProviderFromThisProvider() {
ref.read(otherProvider).someMethodINeedToTrigger();
}
}
final yourProvider = ChangeNotifierProvider<YourProvider>(
(ref) => YourProvider(ref));

Expected a value of type 'Widget', but got one of type 'Null'

i am learning responsive web but i am stuck here in some kind of error like
Expected a value of type 'Widget', but got one of type 'Null'
don't know how to fix this error i tried a lot but i think i can't solve this without someone's help.
import 'package:flutter/material.dart';
const int largeScreenSize = 1366;
const int mediumScreenSize = 768;
const int smallScreenSize = 360;
const int customScreenSize = 1100;
class Responsiveness extends StatelessWidget {
final Widget? largeScreen;
final Widget? mediumScreen;
final Widget? smallScreen;
const Responsiveness({
this.largeScreen,
this.mediumScreen,
this.smallScreen,
});
#override
Widget build(BuildContext context) {
return LayoutBuilder(builder: (context, constraints) {
double _width = constraints.maxWidth;
if (_width >= largeScreenSize) {
return largeScreen as Widget;
} else if (_width >= mediumScreenSize && _width < largeScreenSize) {
return mediumScreen ?? largeScreen as Widget;
} else {
return smallScreen ?? largeScreen as Widget;
}
});
}
}
largeScreen, mediumScreen and smallScreen all have the type Widget?, meaning: "either a Widget or null.
When you return largeScreen as Widget, if largeScreen is null, you will get an error, because null is not a valid value for Widget (just like 123 as String would throw, since the types are not assignable).
Looking at your code, if all 3 of those variables are null, you'd eventually try to return null from the LayoutBuilder parameter, which is always an error, because it returns a non-null Widget.
Make sure you account for the case where all of these are null, or make sure they are never all null at the same time (perhaps with an assert statement).

How to transform flutter BLoC stream snapshot into custom object?

I'm trying to implement BLoC pattern in my flutter app,
basically this app calculate some result and display it in table.
i have created CalculationResultProvider and CalculationResultBloc
as follows
class CalculationResultProvider
{
List<EstimationResult> resultList = new List();
List<EstimationResult> calculateResult(){
return getInitialData(); }
List<EstimationResult> getInitialData(){
var cement = new EstimationResult();
cement.material = "Cement";
cement.unit = "Ton";
cement.qty = 10;
var sand = new EstimationResult();
sand.material = "Sand";
sand.unit = "Ton";
sand.qty = 12;
var gravel = new EstimationResult();
gravel.material = "Gravel";
gravel.unit = "Ton";
gravel.qty = 5;
var steel = new EstimationResult();
steel.material = "Steel";
steel.unit = "Ton";
steel.qty = 5;
List<EstimationResult> resultList = new List();
resultList.add(cement);
resultList.add(sand);
resultList.add(gravel);
resultList.add(steel);
return resultList; } }
and my BLoC provider class as follows
class CalculationResultBloc {
final resultController = StreamController(); // create a StreamController
final CalculationResultProvider provider =
CalculationResultProvider(); // create an instance of our CounterProvider
Stream get getReult =>
resultController.stream; // create a getter for our stream
void updateResult() {
provider
.calculateResult(); // call the method to increase our count in the provider
resultController.sink.add(provider.resultList); // add the count to our sink
}
void dispose() {
resultController.close(); // close our StreamController
}
}
then i need to show this data in table widget
class ResultTableWidget extends StatefulWidget {
#override
State<StatefulWidget> createState() => ResultTableWidgetState();
}
class ResultTableWidgetState extends State {
final bloc =
CalculationResultBloc(); // create an instance of the counter bloc
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: bloc.getReult,
initialData: CalculationResultProvider().getInitialData(),
builder: (context, snapshot) {
DataTable(
columns: [
DataColumn(label: Text('Patch')),
DataColumn(label: Text('Version')),
DataColumn(label: Text('Ready')),
],
rows:
'${snapshot.data}' // Loops through dataColumnText, each iteration assigning the value to element
.map(
((element) => DataRow(
cells: <DataCell>[
DataCell(Text(element[
"Name"])), //Extracting from Map element the value
DataCell(Text(element["Number"])),
DataCell(Text(element["State"])),
],
)),
)
.toList(),
);
});
}
#override
void dispose() {
bloc.dispose();
super.dispose();
}
}
To iterate returning table it should be List<EstimationResult>
but how to transform snapshot in to List<EstimationResult> ?
where is the best place to transform inside bloc class or in widget class ?
im new to dart and flutter , can any one answer my questions?
thanks.
Your widget class will have no clue of the data type given by the stream function in your StreamBuilder, there are many ways to convert data in BloC right before streaming it, but all of them will be useless because to the widget class it's only a snapshot, and the only fields you can access in compilation time are those applied to a generic snapshot like data field. So, The only way to access custom list fields is to provide your StreamBuilder with what type of data to be expected from its stream function :
StreamBuilder<List<EstimationResult>>(
stream: bloc.getReult,
initialData: CalculationResultProvider().getInitialData(),
builder: (context, snapshot) {
//...
}
);
This way you can treat your snapshot as List<EstimationResult>, and have access to inner fields and functions right before you receive the actual snapshot. In your case probably you should import EstimationResult class into your widget class.

In Flutter, how can I define a baseline for a Stateless widget?

I have created a stateless widget, and I want to define a baseline for it, so that I can use it with the Baseline widget (https://docs.flutter.io/flutter/widgets/Baseline-class.html).
How can I do that?
If you want to define a baseline you can't do it directly in the stateless widget. You need to mess around its corresponding RenderBox that needs to implement the computeDistanceToActualBaseline() method.
Give a look to the ListTile implementation here. You will see that the _RenderListTile RenderBox implements the above method returning the baseline of the title widget.
#override
double computeDistanceToActualBaseline(TextBaseline baseline) {
assert(title != null);
final BoxParentData parentData = title.parentData;
return parentData.offset.dy + title.getDistanceToActualBaseline(baseline);
}
In this case, the baseline of the title is the bottom of the Text widget.
All this is needed because the Baseline widget tries to get the baseline of the child widget. If you don't provide a explicit baseline with the above method, it just uses its bottom position.
You can find below an example of a BaselineBox where you can set an arbitrary baseline from top.
import 'package:flutter/widgets.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
class BaselineBox extends SingleChildRenderObjectWidget {
const BaselineBox({Key key, #required this.baseline, Widget child})
: assert(baseline != null),
super(key: key, child: child);
final double baseline;
#override
RenderBaselineBox createRenderObject(BuildContext context) =>
new RenderBaselineBox(baseline: baseline);
#override
void updateRenderObject(
BuildContext context, RenderBaselineBox renderObject) {
renderObject.baseline = baseline;
}
#override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(new DoubleProperty('baseline', baseline));
}
}
class RenderBaselineBox extends RenderProxyBox {
RenderBaselineBox({
RenderBox child,
#required double baseline,
}) : assert(baseline != null),
assert(baseline >= 0.0),
assert(baseline.isFinite),
_baseline = baseline,
super(child);
double get baseline => _baseline;
double _baseline;
set baseline(double value) {
assert(value != null);
assert(value >= 0.0);
assert(value.isFinite);
if (_baseline == value) return;
_baseline = value;
markNeedsLayout();
}
#override
double computeDistanceToActualBaseline(TextBaseline baselineType) {
return _baseline;
}
#override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(new DoubleProperty('baseline', baseline));
}
}

JavaFX binding a BooleanProperty to Paint

I am writing a code that can take some boolean values from a part of some other code and change colours of certain circles on the screen accordingly. However I ran into problems trying to bind the boolean values to colours. I ended up with this:
unit1.getNeuron().getWorkingProperty().addListener(new ChangeListener<Boolean>() {
#Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
if (newValue == Boolean.FALSE) {
controller.paint1 = new ObservableValueBase<Paint>() {
#Override
public Paint getValue() {
return Color.RED;
}
};
} else {
controller.paint1 = new ObservableValueBase<Paint>() {
#Override
public Paint getValue() {
return Color.DODGERBLUE;
}
};
}
}
});
but I'll have to repeat it for n times for n variables I use. Is there a different way to implement this?
Let' s say you want to create an ObservableObjectValue<Paint> you want to toggle based on an ObservableBooleanValue, then Bindings is your friend:
final ObservableBooleanValue booleanCondition = unit1.getNeuron().getWorkingProperty();
final ObservableObjectValue<Paint> paintProperty = Bindings.when(booleanCondition).then(Color.RED).otherwise(Color.DODGERBLUE);

Resources