How can I get an extension method to change the original object in Dart? - flutter

I have bounds. bounds should be extended with new LatLng.
var bounds = LatLngBounds();
for (var latlng in _list) bounds.extend(latlng);
I want to implement this extension:
extension LatLngBoundsExtend on LatLngBounds {
extend(LatLng _latLng){
//I want to change the object which this method is called from
this = LatLngBounds(southwest: /* some magic code*/, northeast: /* some magic code*/ );
}
}

It would have been possible to change the original object if its properties were not final. This is not the case for LatLngBounds.southwest and LatLngBounds.northeast.

If I remember correctly, LatLngBounds is an immutable class representing a latitude/longitude aligned rectangle.
final myLatLngBounds = LatLngBounds(southwest, northeast);
myLatLngBounds.extend(latlng);
If this extend modifies this how would you access this? 😮
I think this extend should rather be part of some kind of State Management.
See the following, using Riverpod Hooks package:
Instead of changing the object (since it is immutable), you return a new one in your extension:
extension RectX on Rect {
Rect extend(Offset offset) {
return Rect.fromLTRB(
min(left, offset.dx),
min(top, offset.dy),
max(right, offset.dx),
max(bottom, offset.dy),
);
}
}
Then, when you need to modify the object, you work on a State Object managed by Flutter or any other State Management System:
final rectProvider =
StateNotifierProvider<RectNotifier>((ref) => RectNotifier());
class RectNotifier extends StateNotifier<Rect> {
RectNotifier([Rect state])
: super(state ?? Rect.fromLTRB(100, 100, 200, 200));
void extend(Offset offset) {
state = state.extend(offset);
}
}
Here is a Minimal Working Example:
import 'dart:math' show min, max;
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
void main() {
runApp(
ProviderScope(
child: MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
home: HomePage(),
),
),
);
}
class HomePage extends HookWidget {
#override
Widget build(BuildContext context) {
final rect = useProvider(rectProvider.state);
return Scaffold(
body: GestureDetector(
onTapDown: (details) =>
context.read(rectProvider).extend(details.localPosition),
child: Container(
color: Colors.black12,
padding: EdgeInsets.all(16.0),
child: Stack(
children: [
Positioned(
top: rect.top,
left: rect.left,
child: Container(
width: rect.width,
height: rect.height,
color: Colors.amber.shade400),
),
],
),
),
),
);
}
}
final rectProvider =
StateNotifierProvider<RectNotifier>((ref) => RectNotifier());
class RectNotifier extends StateNotifier<Rect> {
RectNotifier([Rect state])
: super(state ?? Rect.fromLTRB(100, 100, 200, 200));
void extend(Offset offset) {
state = state.extend(offset);
}
}
extension RectX on Rect {
Rect extend(Offset offset) {
return Rect.fromLTRB(
min(left, offset.dx),
min(top, offset.dy),
max(right, offset.dx),
max(bottom, offset.dy),
);
}
}

You can change the fields if they are not final. But you cannot change object as this = new Class().
PS.
southwest and northeast are final fields of LatLngBounds. So you can't change them with an extension
https://github.com/flutter/plugins/blob/master/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/location.dart

Extension methods can mutate this, but they cannot reassign/rebind it. Note that that is true for normal methods too and isn't specific to extension methods.
Suppose that reassigning this were possible. Consider the following code:
var foo = Foo();
var sameFoo = foo;
foo.reassignThis();
Would foo and sameFoo still refer to the same object? It would be surprising if they now referred to different objects. However, if they do still refer to the same object, that would mean that the VM/runtime would need to be able to easily and quickly find all references to an object so that it can update them.
Even if the VM/runtime could do that, then consider:
class Base {
Base(this.x);
int x;
void reassignThis() {
this = Base(x + 1); // Not legal!
}
}
class Derived extends Base {
Derived(int x) : super(x);
int y;
}
void main() {
var derived = Derived(0);
derived.reassignThis();
print(derived.y);
}
After calling reassignThis(), what would derived be? Would it still be a Derived object? Would it be just a Base object?
Reassigning this isn't possible, and it isn't something that could be improved; fundamentally it doesn't make much sense.
Now, instead of reassigning this, you could implement something like:
class MyLatLngBounds implements LatLngBounds {
MyLatLngBounds(this._actual);
LatLngBounds _actual;
LatLng get northeast => _actual.northeast;
LatLng get southwest => _actual.southwest;
// ... other methods that forward to [_actual] ...
void extend(LatLng _latLng) {
_actual = LatLngBounds(/* some magic code */);
}
}
and then try to use MyLatLngBounds everywhere instead of LatLngBounds.

Related

Navigate to a flutter widget from a flame game without losing the game state

My flame game concept is that a player moves, and when the player collides with a star object, a pop up flutter window contains a question shall appear, and when the user closes it, the game state and player position will be restored.How can I do so?
I tried Navigator.push but it doesn't work, it says that no context available :(
Also I tried a different way, which is the overlay, but the Star class cannot access the overlays property, I wrote a method in the main game class "PlayerWithBG" but calling it throws an exception...
This is the code inside Star class
import 'package:flame/collisions.dart';
import 'package:flame/components.dart';
import 'package:flame/sprite.dart';
import 'package:flutter/material.dart';
import 'package:khalooq/Game/PlayerWithBG.dart';
import '../Questions/firstLevelQuestions.dart';
import 'helpers/directions.dart';
class Star extends SpriteAnimationComponent
with HasGameRef, CollisionCallbacks {
Star(Vector2 position)
: super(size: Vector2.all(50), position: position, anchor: Anchor.center);
final gameObject = PlayerWithBG();
late final SpriteAnimation _rotateAnimation;
final double _animationSpeed = .3;
Direction direction = Direction.right;
#override
Future<void> onLoad() async {
super.onLoad();
await _loadAnimations().then((_) => {animation = _rotateAnimation});
add(CircleHitbox());
}
Future<void> _loadAnimations() async {
final spriteSheet = SpriteSheet.fromColumnsAndRows(
image: await gameRef.images.load('stars1.png'), columns: 10, rows: 1);
_rotateAnimation = spriteSheet.createAnimation(
row: 0, stepTime: _animationSpeed, from: 0, to: 4);
}
#override
void onCollision(Set<Vector2> intersectionPoints, PositionComponent other) {
super.onCollision(intersectionPoints, other);
// gameObject.showQuestion();
// calling the showQuestion() method throws an exception in the method
/*
* Exception has occurred.
_AssertionError ('package:flame/src/game/overlay_manager.dart': Failed assertion: line 51 pos 7: '_builders.containsKey(name)': Trying to add an unknown overlay "Question")
*/
removeFromParent();
//This is what I want to do--------------------------------------------------
// Navigator.push(
// context, MaterialPageRoute(builder: (context) => firstLevelQuestion()));
}
}
And this is the code inside the main game class "PlayerWithBG"
import 'dart:ui';
import 'package:flame/game.dart';
import 'package:flame/input.dart';
import 'package:flutter/services.dart';
import 'Player.dart';
import 'GameBG.dart';
import 'package:flutter/material.dart';
import 'Star.dart';
import 'helpers/directions.dart';
class PlayerWithBG extends FlameGame
with KeyboardEvents, HasCollisionDetection, TapDetector {
Player _player = Player();
GameBG _gameBG = GameBG();
#override
Future<void> onLoad() async {
super.onLoad();
await add(_gameBG);
double bgWidth = _gameBG.size.x;
double bghight = _gameBG.size.y;
await add(_player);
_player.position = Vector2(bgWidth * 0.05, bghight * 0.95);
camera.followComponent(_player,
worldBounds: Rect.fromLTRB(0, 0, _gameBG.size.x, _gameBG.size.y));
//--------------------------------------------------------------------------------------
//Stars are the elements that should open the question when the player collides with them
add(Star(Vector2(bgWidth * 0.10, bghight * 0.95)));
add(Star(Vector2(bgWidth * 0.30, bghight * 0.95)));
}
void showQuestion() {
if (overlays.isActive('Question')) {
overlays.remove('Question');
resumeEngine();
} else {
overlays.add('Question');
pauseEngine();
}
}
onArrowKeyChanged(Direction direction) {
_player.direction = direction;
}
}
And here where I call the game widget with a temp overlay container
GameWidget(
game: game..paused = false,
//here is the overlayer to render the question--------------------
overlayBuilderMap: {
'Question': (BuildContext context, PlayerWithBG game) {
return Center(
child: Container(
width: 100,
height: 100,
color: Colors.orange,
child: Center(child: Text('This is a question')),
),
);
},
},
//-----------------------------------------------------------------
),
You can do this in a few different ways:
1.Use the overlays system
Just like you tried, but to access the overlays you can use the fact that you have the HasGameRef mixin on your component, which gives your access to the gameRef variable and thus gameRef.overlays.add/remove.
You can read more about that here.
2. Use the RouterComponent
This technique is slightly more advanced, but it is definitely worth exploring if you will have a lot of different screens and scenes.
Example:
final router = RouterComponent(
routes: {
'ok-dialog': OverlayRoute(
(context, game) {
return Center(
child: DecoratedContainer(...),
);
},
), // OverlayRoute
'confirm-dialog': OverlayRoute.existing(),
},
);
router.pushNamed('ok-dialog);
You can read a lot more about that here.
3. Use the normal Flutter navigation
The last, but least ergonomic way, is to use Flutter's built-in navigation (or another navigation package).
You can get the context in the same way as you accessed the overlays, when you have the HasGameRef mixin:
gameRef.buildContext;
Depending on how you do the navigation you might have to use the GameWidget.controlled constructor when you create your GameWidget.

Flutter syncfusion Linechart re-draw line between beginning and ending series data

I use syncfusion chart for flutter. I have json data on php api at my server.
I had this data in flutter api connect.
My json data structure is like this:
{
"tablo": "neyzi",
"cinsiyet": "erkek",
"boy": {
"P3": [
{
"0.0": 45.9,
"3.0": 56.2,
"6.0": 62.8,
"9.0": 67.4,
"12.0": 70.8,
"15.0": 73.8,
"18.0": 76.4
}
],
},
}
I use this code for prepare data for chart:
import 'package:flutter/material.dart';
import 'package:pediatrirutinmobil/pers_chart/chart_olcumdizisi.dart';
import 'package:syncfusion_flutter_charts/charts.dart';
import 'chart_api.dart';
class PersentilChartRepository{
static PersentilChartApiClient _persentilchartApiClient = PersentilChartApiClient();
static List<OlcumDizisi> _p3 =[];
static Future apiden_data_getir ()async{
return await _persentilchartApiClient.veriyigetir();
}
static Future<List<OlcumDizisi>> persentilListesi ()async{
}
static List boyListesi() {
apiden_data_getir().then((value) async{
var P3e = await value.boy.P3[0];
for (final mapEntry in P3e.entries) {
final key = await double.parse(mapEntry.key.toString());
final double value = await double.parse(mapEntry.value.toString());
if (key<=limit){
_p3.add(OlcumDizisi(key, value));
}
}
// _p3.addAll([OlcumDizisi(6,60),OlcumDizisi(7, 80),OlcumDizisi(10, 90)]);
*/
} );
List<ChartSeries<OlcumDizisi,double>> chartSeries = [
new LineSeries<OlcumDizisi, double>(
name: 'P3',
xValueMapper: (OlcumDizisi olcum, _) => olcum.yasay,
yValueMapper: (OlcumDizisi olcum, _) => olcum.olcum,
dataSource: _p3,
color: Colors.red,
width: 0.75,
)
];
return chartSeries;
}
}
class OlcumDizisi {
final double yasay;
final double olcum;
OlcumDizisi(this.yasay, this.olcum);
}
And I use chart page like this:
import 'dart:core';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:pediatrirutinmobil/pers_chart/chart_repo.dart';
import 'package:syncfusion_flutter_charts/charts.dart';
class StackedAreaLineChart extends StatefulWidget {
#override
State<StackedAreaLineChart> createState() => _StackedAreaLineChartState();
}
class _StackedAreaLineChartState extends State<StackedAreaLineChart> {
List _charset;
#override
void initState() async{
_charset = await PersentilChartRepository.boyListesi();
setState(() {
});
// TODO: implement initState
super.initState();
}
#override
void dispose() {
_charset;
// TODO: implement dispose
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: SafeArea(
child: Center(
child: Container(
child: SfCartesianChart(
primaryXAxis: CategoryAxis(),
primaryYAxis: NumericAxis(
numberFormat: NumberFormat.decimalPattern()
),
// Chart title
title: ChartTitle(text: 'TITLE'),
// Enable legend
legend: Legend(isVisible: true),
// Enable tooltip
tooltipBehavior: TooltipBehavior(enable: true),
series:_charset,
enableSideBySideSeriesPlacement: false,
),
),
),
),
);
}
}
so after then all of this page my chart build graphic but if I back another page and reopen chart page Linechart re-build new line beginning point and ending point.
like this:
this
and this
If i use static List data in chart page its perfect but i use static data in future code like this
apiden_data_getir().then((value) async{
_p3.addAll([OlcumDizisi(6,60),OlcumDizisi(7, 80),OlcumDizisi(10, 90)]);
} );
final result same...
is there any idea.
If you have different solution binding api line chart so I thank you for it.
We talk on github and
static Future apiden_data_getir() async {
///this code
if (_p3.isNotEmpty) {
_p3.clear();
}
/// helpfull it's work
final String jsonString = await getJsonFromAssets();
final dynamic jsonResponse = json.decode(jsonString);
var p3e = jsonResponse['boy']['P3'][0];
The problem seems to be with your init state.
Every time you visit the graph page, the same data gets added to the data source repeatedly, creating a closed loop within the graph. You can verify that by putting a debugger point at this line series:_charset to see the repeating values added to the series.
Try wrapping your SfCartesianChart widget with a Future builder and fetching data there instead of making an API call in the initState.

Widget not updating flutter

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

Flutter: Countdown Timer

I am trying to create a timer app that has multiple countdown timers for different tasks. The issue, I am facing is that, if I start a one-timer, and press the back button, the timer stops. So I want, that timer to run till either it is being paused or the timer ends and alerts the user or the app is destroyed. Help me how can I do this using Flutter?
Any Sample Code Will be Appreciated?
enter link description here
CountDownController _controller = CountDownController();
CircularCountDownTimer(
width: MediaQuery.of(context).size.width / 6,
height: MediaQuery.of(context).size.width / 6,
duration: 120,
fillColor: Colors.green,
ringColor: Colors.white,
controller: _controller,
backgroundColor: Colors.white54,
strokeWidth: 10.0,
strokeCap: StrokeCap.round,
isTimerTextShown: true,
isReverse: false,
onComplete: () {
Notify();
},
textStyle: TextStyle(fontSize: 20.0, color:
Colors.black),
),
When you pop back, any "state" in the widget will be destroyed.
There are three kinds of method you can do to prevent "state" being destroyed (or memory release):
Using static property
Using state manager by Provider
Using state manager by static instance
There are still many method to manage your state, but not mention here, see details in this repo
Static property
Static property is something like variable outside your class, like:
// prefix "_" for private variable
const _myStaticVar = 'hello world';
class MyWidget {}
Rather, it is class based variable. Which means it can help you describe the variable more. like class Dog can has a static property static final footShouldHave = 4. Class based programing is popular because it can manage your state and any logic action "inside" the class, and make it easier to understand and code.
When the class is being destroyed (memory release), any "state" inside the class should be pop from stack but not static. You can see more detail by knowing how compiler works.
In your case, you can do something like:
class MyTimer extends StatlessWidget {
static DateTime? starter;
Widget build(context) {
if (starter == null) {
starter = DateTime.now();
}
final secondPass = (DateTime.now().millisecondsSinceEpoch - starter!.millisecondsSinceEpoch) / 1000;
final secondLeft = 60 - secondPass;
return Text(secondLeft.toString());
}
}
Provide state manager by Provider
Provider is made for flutter and also maintained by flutter team. It can make you easy to manage your class by accessing it from context.
You can also set up how the class create.
lazy, create only when you need it
create in future
...
In your case, it should be like:
Build your helper class TimerManager
class TimerManager {
final DateTime? starter;
void startIfNeed() {
if (starter != null) {
starter = DateTime.now();
}
}
num get secondLeft => 60 - (DateTime.now().millisecondsSinceEpoch - starter!.millisecondsSinceEpoch) / 1000
}
Bind with Provider
class Homepage extends statelessWidget {
Widget build(context) {
return TextButton(
onPressed: () => navigateToTimer(context),
child: Text('go'),
);
}
void navigateToTimer(Build context) {
Navigator.of(context).push(
MaterialPageRoute(builder: (_) => MyTimer()),
);
}
}
void main() {
runApp(MaterialApp(
home: Provider<TimerManager>(
create: () => TimerManager(),
child: Homepage(),
)
));
}
Get it from your context.
Now when your widget is released, it is still existed in parent context (if their do exist a parent).
// remember to import provider to able `context.read()`.
// see more detail in document.
import 'package:provider/provider.dart';
class MyTimer extends StatlessWidget {
Widget build(context) {
final manager = context.read<TimerManager>();
manager.startIfNeed();
return Text(manager.secondLeft.toString());
}
}
static instance
Kind of combined method from 1 and 2.
class TimerManager {
// make it singleton
static final TimerManager instance = TimerManager._();
// It is now private constructor
const TimerManager._();
...
}
Just call it in your widget
class MyTimer extends StatlessWidget {
Widget build(context) {
TimerManager.instance.startIfNeed();
return Text(TimerManager.instance.secondLeft.toString());
}
}
Summary
There is no best way to keep your state in generally, but in your case, I recommend Provider method.

Riverpod: List provider is not rebuilding

Flutter riverpod is not notifying the Consumer on the state change when the StateNotifier's type is List, while the same implementation works just fine for other types.
here, I provided a minimal reproducable example:
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ProviderScope(
child: MaterialApp(
home: MyHomePage(),
),
);
}
}
class CounterState extends StateNotifier<List<int>> {
static final provider = StateProvider(
(ref) => CounterState(),
);
int get last {
print('last');
return state.last;
}
int get length {
print('len');
return state.length;
}
// the body of this will be provided below
add(int p) {}
CounterState() : super(<int>[0]);
}
class MyHomePage extends ConsumerWidget {
#override
Widget build(BuildContext context, watch) {
void _incrementCounter() {
final _count = Random.secure().nextInt(100);
context.read(CounterState.provider.notifier).state.add(_count);
}
var count = watch(CounterState.provider.notifier).state.length;
return Scaffold(
appBar: AppBar(),
body: Center(
child: Text(
'You have pushed the button this many times: $count',
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
child: Icon(Icons.add),
),
);
}
}
as for the add method, I tried implementing it in a lot of ways, but neither works.
here is what I tried:
1: just add it straight away:
add(int p) {
state.add(p);
}
2: I also tried the solution suggested in this answer:
add(int p) {
state = [...state, p];
}
3: I tried to destroy the list entirely, and reassign it:
add(int p) {
final _state = [];
// copy [state] to [_state]
for (var item in state) {
_state.add(item);
}
// empty the state
state = [];
// add the new element
_state.add(p);
// refill [state] from [_state]
for (var item in _state) {
state.add(item);
}
print(state.length); // it continues until here and prints
}
Firstly, you are not creating the correct provider to listen to a StateNotifier. You need to change this:
static final provider = StateProvider(
(ref) => CounterState(),
);
to this:
static final provider = StateNotifierProvider<CounterState, List<int>>(
(ref) => CounterState(),
);
Please refer to the Riverpod documentation about the different types of providers.
Secondly, you are not actually watching for state changes, but you are just getting the state object from the notifier.
Change this line:
var count = watch(CounterState.provider.notifier).state.length;
to this:
final count = watch(CounterState.provider).length;
also, your increment method is not correct for StateNotifier providers. Please change this:
context.read(CounterState.provider.notifier).state.add(_count);
to this:
context.read(CounterState.provider.notifier).add(_count);
It should rebuild now when the state changes. However, you do need an implementation of your add method that actually changes the state object itself. I would suggest the second variant you mentioned, that is in my opinion the nicest way to do this:
add(int p) {
state = [...state, p];
}
#TmKVU explained well, so I'm skipping that part. You can also follow riverpod document.
here is my example of riverPod:
stateNotifierProvider
stateProvider
Your widget
import 'dart:math';
import 'package:stack_overflow/exports.dart';
class CounterState extends StateNotifier<List<int>> {
static final provider = StateNotifierProvider(
(ref) => CounterState(),
);
int get last {
print('last');
return state.last;
}
int get length {
print('len');
return state.length;
}
// the body of this will be provided below
add(int p) {}
CounterState() : super(<int>[0]);
}
class MyHomePageSSSS extends ConsumerWidget {
#override
Widget build(BuildContext context, watch) {
void _incrementCounter() {
final _count = Random.secure().nextInt(100);
context.read(CounterState.provider.notifier).state =
context.read(CounterState.provider.notifier).state..add(_count);
}
final countprovider = watch(CounterState.provider);
return Scaffold(
appBar: AppBar(),
body: Center(
child: Text(
'You have pushed the button this many times: ${countprovider.length}',
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
child: Icon(Icons.add),
),
);
}
}