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

Viewed 694

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*/   );

  }
}
4 Answers

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:

enter image description here

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

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