Created
September 22, 2020 00:29
-
-
Save roipeker/fc6ba426a72c1aa59406ec4773b05cd0 to your computer and use it in GitHub Desktop.
UIController, local state concept to replace stateful (not working properly)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'dart:async'; | |
import 'package:flutter/cupertino.dart'; | |
import 'package:flutter/material.dart'; | |
import 'package:get/get.dart'; | |
import 'package:roi_getx_samples/custom_widget/ui_builder.dart'; | |
extension MyVisible on Widget { | |
visible(bool flag, {bool maintainSize = false}) { | |
return Visibility( | |
child: this, | |
maintainState: true, | |
maintainAnimation: true, | |
maintainInteractivity: false, | |
maintainSize: maintainSize, | |
visible: flag, | |
); | |
} | |
} | |
main() { | |
runApp(GetMaterialApp( | |
initialBinding: BindingsBuilder(() { | |
Get.put(_MyHomeController()); | |
}), | |
home: _MyHome(), | |
)); | |
} | |
class MyModel { | |
final active = false.obs; | |
final int index; | |
var key = UniqueKey(); | |
MyModel(this.index); | |
} | |
class _MyHomeController extends GetxController { | |
final showing = false.obs; | |
final myToggles = List.generate(3, (index) => MyModel(index)).obs; | |
removeIndex(int index) { | |
myToggles.removeWhere((m) => m.index == index); | |
} | |
removeModel(MyModel model) { | |
myToggles.remove(model); | |
} | |
} | |
class _MyHome extends GetView<_MyHomeController> { | |
@override | |
Widget build(BuildContext context) { | |
return Scaffold( | |
body: Column( | |
children: [ | |
Expanded( | |
child: Obx( | |
() => | |
///OPTION 1 | |
ListView.separated( | |
itemBuilder: (ctx, idx) { | |
final model = controller.myToggles[idx]; | |
return Obx( | |
() => MySwitch( | |
key: model.key, | |
index: model.index, | |
onChange: model.active, | |
onRemove: () => controller.removeModel(model), | |
// onRemove: controller.removeIndex, | |
isActive: model.active(), | |
), | |
); | |
}, | |
separatorBuilder: (ctx, idx) => Divider(height: 1), | |
itemCount: controller.myToggles.length, | |
), | |
/// OPTION 2 | |
// ListView( | |
// children: controller.myToggles | |
// .map( | |
// (model) => MySwitch( | |
// key: model.key, | |
// onChange: model.active, | |
// index: model.index, | |
// onRemove: () => controller.removeModel(model), | |
// isActive: model.active(), | |
// ), | |
// ) | |
// .toList(), | |
// ), | |
), | |
), | |
], | |
), | |
); | |
} | |
} | |
class MySwitchController extends UIController<MySwitch> { | |
final rebuilds = 0.obs; | |
MySwitchController() { | |
print("new MySwitchController()"); | |
} | |
@override | |
void didUpdateWidget(MySwitch oldie, MySwitch newbie) { | |
if (oldie != newbie) { | |
rebuilds.value++; | |
} | |
} | |
@override | |
didChangeDependencies(s) { | |
print("CHANGE DEP"); | |
} | |
bool isActive = false; | |
void toggleActive() { | |
isActive = !isActive; | |
update(); | |
} | |
@override | |
onInit() { | |
print("Initializing $hashCode"); | |
} | |
@override | |
FutureOr onClose() { | |
print('on closed'); | |
rebuilds.close(); | |
// Get.snackbar('Item borrado', 'Item ${widget.index} removed'); | |
// print('Item borrado - Item ${widget.index} removed'); | |
} | |
} | |
class MySwitch extends UIView<MySwitchController> { | |
final bool isActive; | |
final Function(bool) onChange; | |
final Function() onRemove; | |
// final Function(int) onRemove; | |
final int index; | |
const MySwitch({ | |
Key key, | |
this.index, | |
this.isActive = false, | |
this.onChange, | |
this.onRemove, | |
}) : super(key: key); | |
@override | |
get params => [isActive, index]; //onChange, | |
@override | |
get initController => MySwitchController(); | |
@override | |
Widget render(controller) { | |
return Container( | |
height: 30, | |
child: Row( | |
crossAxisAlignment: CrossAxisAlignment.stretch, | |
children: [ | |
Container( | |
color: Colors.black, | |
width: 90, | |
alignment: Alignment.center, | |
child: Obx(() { | |
final rebuilds = controller.rebuilds.toString().padLeft(2, '0'); | |
return Text( | |
'$index - rebuilds: $rebuilds ', | |
style: const TextStyle( | |
fontSize: 10, | |
fontWeight: FontWeight.bold, | |
color: Colors.white, | |
), | |
); | |
}), | |
), | |
const VerticalDivider(width: 1), | |
GestureDetector( | |
onTap: () => onChange.call(!isActive), | |
child: _buildBox('WIDGET', isActive), | |
), | |
const VerticalDivider(width: 1), | |
GestureDetector( | |
onTap: controller.toggleActive, | |
child: _buildBox('CONTROLLER', controller.isActive), | |
), | |
FlatButton( | |
// onPressed: () => onRemove(index), | |
onPressed: onRemove, | |
minWidth: 48, | |
child: Icon(Icons.delete), | |
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, | |
padding: EdgeInsets.zero, | |
), | |
], | |
), | |
); | |
} | |
Widget _buildBox(String label, bool flag) { | |
return Container( | |
width: 90, | |
color: flag ? Colors.green : Colors.red, | |
alignment: Alignment.center, | |
child: Text( | |
label, | |
style: TextStyle( | |
fontSize: 10, | |
fontWeight: FontWeight.bold, | |
color: Colors.white, | |
), | |
), | |
); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'package:flutter/material.dart'; | |
import 'package:get/get.dart'; | |
abstract class EqualView { | |
List<Object> get params => null; | |
bool equals(EqualView o) { | |
if (params == null || o.params == null) return false; | |
for (var i = 0; i < params.length; ++i) { | |
if (params[i] != o.params[i]) return false; | |
} | |
return true; | |
} | |
} | |
abstract class UIView<T extends UIController> extends StatelessWidget { | |
// with EqualView { | |
const UIView({Key key}) : super(key: key); | |
List<Object> get params => null; | |
bool equals(UIView o) { | |
if (params == null || o.params == null) return false; | |
for (var i = 0; i < params.length; ++i) { | |
if (params[i] != o.params[i]) return false; | |
} | |
return true; | |
} | |
@override | |
// ignore: invalid_override_of_non_virtual_member | |
bool operator ==(Object other) { | |
if (params != null && other is UIView) { | |
return equals(other); | |
} | |
return super == other; | |
} | |
@override | |
// ignore: invalid_override_of_non_virtual_member | |
int get hashCode { | |
if (params != null) { | |
return params.fold( | |
0, (previousValue, element) => previousValue ^ element.hashCode); | |
} | |
return super.hashCode; | |
} | |
T get initController; | |
@override | |
Widget build(BuildContext context) { | |
return UIBuilder<T, UIView>( | |
owner: this, | |
init: () => initController, | |
builder: (controller) => render(controller), | |
); | |
} | |
Widget render(T controller) { | |
return Container(); | |
} | |
} | |
class UIController<T> extends GetxController { | |
void didChangeDependencies(State state) {} | |
void didUpdateWidget(T oldWidget, T newWidget) {} | |
void _didUpdateWidget(T oldWidget, T newWidget) { | |
_widget = newWidget; | |
didUpdateWidget(oldWidget, newWidget); | |
} | |
T _widget; | |
T get widget => _widget; | |
} | |
class UIBuilder<T extends UIController, S extends UIView> | |
extends StatefulWidget { | |
final Widget Function(T) builder; | |
final String id; | |
final bool autoRemove; | |
final void Function(State state) initState, dispose, didChangeDependencies; | |
final void Function(UIBuilder oldWidget, State state) didUpdateWidget; | |
final InstanceBuilderCallback<T> init; | |
final S owner; | |
const UIBuilder({ | |
Key key, | |
this.owner, | |
this.init, | |
@required this.builder, | |
this.autoRemove = true, | |
this.initState, | |
this.dispose, | |
this.id, | |
this.didChangeDependencies, | |
this.didUpdateWidget, | |
}) : assert(builder != null), | |
super(key: key); | |
@override | |
_GetBuilderState<T, S> createState() => _GetBuilderState<T, S>(); | |
} | |
class _GetBuilderState<T extends UIController, S extends UIView> | |
extends State<UIBuilder<T, S>> with GetStateUpdaterMixin { | |
VoidCallback remove; | |
// T get controller => widget.init; | |
UIController _controller; | |
@override | |
void initState() { | |
super.initState(); | |
if (widget.initState != null) widget.initState(this); | |
_controller = widget.init.call(); | |
_controller?.onStart(); | |
_subscribeToController(); | |
} | |
/// Register to listen Controller's events. | |
/// It gets a reference to the remove() callback, to delete the | |
/// setState "link" from the Controller. | |
void _subscribeToController() { | |
remove?.call(); | |
remove = (widget.id == null) | |
? _controller?.addListener(getUpdate) | |
: _controller?.addListenerId(widget.id, getUpdate); | |
} | |
/// Sample for [GetStateUpdate] when you don't wanna | |
/// use [GetStateHelper mixin]. | |
/// bool _getUpdater() { | |
/// final _mounted = mounted; | |
/// if (_mounted) setState(() {}); | |
/// return _mounted; | |
/// } | |
@override | |
void dispose() { | |
super.dispose(); | |
if (widget.dispose != null) widget.dispose(this); | |
if (widget.autoRemove) { | |
_controller?.onClose(); | |
} | |
remove?.call(); | |
} | |
@override | |
void didChangeDependencies() { | |
super.didChangeDependencies(); | |
_controller.didChangeDependencies(this); | |
if (widget.didChangeDependencies != null) { | |
widget.didChangeDependencies(this); | |
} | |
} | |
@override | |
void didUpdateWidget(oldWidget) { | |
super.didUpdateWidget(oldWidget); | |
if (oldWidget.id != widget.id) { | |
_subscribeToController(); | |
} | |
_controller._didUpdateWidget(oldWidget.owner, widget.owner); | |
if (widget.didUpdateWidget != null) widget.didUpdateWidget(oldWidget, this); | |
} | |
@override | |
Widget build(BuildContext context) => widget.builder(_controller); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment