Last active
August 26, 2025 20:21
-
-
Save danReynolds/2bd4e34b2ba48e44283e05b5599c55fe to your computer and use it in GitHub Desktop.
Flutter view controller/model gist
This file contains hidden or 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
class ExampleViewModel { | |
final String name; | |
final String description; | |
final bool editMode; | |
ExampleViewModel({ required this.name, required this.description, required this.editMode}); | |
} | |
class ExampleViewController extends ViewController<ExampleViewModel> { | |
final _editMode = Computable(model.editMode); | |
final _name = Computable(model.name); | |
final _description = Computable(model.description); | |
ExampleViewController() : super( | |
ExampleViewModel(editMode: false, name: 'This is a title', description: 'This is a description'), | |
) { | |
forward( | |
Computable.compute3( | |
_name, | |
_description, | |
_editMode | |
(name, description, editMode) { | |
return ExampleViewModel( | |
name: name, | |
description: description, | |
editMode: editMode, | |
); | |
} | |
) | |
); | |
} | |
void editName(String updatedName) { | |
_name.add(updatedName); | |
} | |
void editDescription(String updatedDescription) { | |
_description.add(updatedDescription); | |
} | |
void _toggleEditMode() { | |
_editMode.add(!model.editMode); | |
} | |
} |
This file contains hidden or 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
class ExampleScreen extends StatelessWidget { | |
@override | |
build(context) { | |
return ViewControllerBuilder( | |
() => ExampleViewController(), | |
(context, controller) { | |
final ExampleViewModel(:name, :description, :isEditing) = controller.model; | |
return Column( | |
children: [ | |
Input( | |
label: 'name', | |
initialValue: name, | |
onChange: (updatedName) { | |
controller.editName(updatedName); | |
} | |
), | |
Input( | |
label: 'description', | |
initialValue: description, | |
onChange: (updatedDescription) { | |
controller.editDescription(updatedDescription); | |
} | |
), | |
Button( | |
title: 'Toggle edit mode', | |
onTap: controller.toggleEditMode, | |
), | |
], | |
); | |
} | |
); | |
} | |
} |
This file contains hidden or 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:computables/computables.dart'; | |
class ViewController<T> extends ForwardingComputable<T> { | |
final List<StreamSubscription> _subscriptions = []; | |
ViewController(super.initialValue) : super(dedupe: false); | |
/// Subscribes to the given computable for the duration of the view controller. Automatically unsubscribes | |
/// from it when the controller is disposed. | |
/// | |
/// Note that this is an *asynchronous* subscription so if the subscription modifies the controller's model, it will | |
/// not be immediately reflected. | |
void subscribe<S>(Computable<S> computable, void Function(S value) listener) { | |
_subscriptions.add(computable.stream().listen(listener)); | |
} | |
@override | |
dispose() { | |
super.dispose(); | |
for (final subscription in _subscriptions) { | |
subscription.cancel(); | |
} | |
} | |
T get model { | |
return get(); | |
} | |
} |
This file contains hidden or 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:computables_flutter/widgets/computable_builder.dart'; | |
import 'package:flutter/material.dart'; | |
typedef _ViewControllerBuilderFn<T extends ViewController> = Widget Function( | |
BuildContext context, | |
T controller, | |
); | |
class ViewControllerBuilder<T extends ViewController> extends StatefulWidget { | |
final T Function() factory; | |
final _ViewControllerBuilderFn<T> builder; | |
final bool autoDispose; | |
const ViewControllerBuilder( | |
this.factory, { | |
required this.builder, | |
this.autoDispose = true, | |
}); | |
@override | |
ViewControllerBuilderState<T> createState() => | |
ViewControllerBuilderState<T>(); | |
static ViewControllerBuilder<T> of<T extends ViewController>( | |
T controller, | |
_ViewControllerBuilderFn<T> builder, | |
) { | |
return ViewControllerBuilder( | |
() => controller, | |
builder: builder, | |
autoDispose: false, | |
); | |
} | |
} | |
class ViewControllerBuilderState<T extends ViewController> | |
extends State<ViewControllerBuilder<T>> { | |
late final T controller; | |
@override | |
initState() { | |
super.initState(); | |
controller = widget.factory(); | |
} | |
@override | |
dispose() { | |
if (widget.autoDispose) { | |
controller.dispose(); | |
} | |
super.dispose(); | |
} | |
@override | |
build(context) { | |
return ComputableBuilder( | |
computable: controller, | |
builder: (context, _) { | |
return widget.builder(context, controller); | |
}, | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment