Note, mvc_pattern has been rebranded, StateX. This package here is soon deprecated.
In keeping with the "KISS Principle", this is an attempt to offer the MVC design pattern to Flutter in an intrinsic fashion incorporating much of the Flutter framework itself. All in a standalone Flutter Package.
In truth, this all came about only because I wanted a place to put my 'mutable' code (the business logic for the app) without the compiler complaining about it! Placing such code in a StatefulWidget or a StatelessWidget is discouraged of course--only immutable code should be in those objects. Sure, all that code could go into the State object. That's good since you want access to the State object anyway. After all, it's the main player when it comes to 'State Management' in Flutter. However, it makes for rather big and messy State objects!
Placing the code in separate Dart files would be the solution, but then there would have to be a means to access that ever-important State object. I wanted the separate Dart file or files that had all the functionality and capability of the State object. In other words, that separate Dart file would to have access to a State object!
Now, I had no interest in re-inventing the wheel. I wanted to keep it all Flutter, and so I stopped and looked at Flutter closely to see how to apply some already known design pattern onto it. That's when I saw the State object (its build() function specifically) as 'The View,' and the separate Dart file or files with access to that State object as 'The Controller.'
This package is essentially the result, and it involves just two 'new' classes: StateMVC and ControllerMVC. A StateMVC object is a State object with an explicit life-cycle (Android developers will appreciate that), and a ControllerMVC object can be that separate Dart file with access to the State object (StateMVC in this case). All done with Flutter objects and libraries---no re-inventing here. It looks and tastes like Flutter.
Indeed, it just happens to be named after the 'granddaddy' of design patterns, MVC, but it's actually a bit more like the PAC design pattern. In truth, you could use any other architecture you like with it. By design, you can just use the classes, StateMVC, and ControllerMVC. Heck! You could call objects that extend ControllerMVC, BLoC's for all that matters! Again, all I wanted was some means to bond a State object to separate Dart files containing the 'guts' of the app. I think you'll find it useful.
Installing
I don't always like the version number suggested in the 'Installing' page. Instead, always go up to the 'major' semantic version number when installing my library packages. This means always entering a version number trailing with two zero, '.0.0'. This allows you to take in any 'minor' versions introducing new features as well as any 'patch' versions that involves bugfixes. Semantic version numbers are always in this format: major.minor.patch.
- patch - I've made bugfixes
- minor - I've introduced new features
- major - I've essentially made a new app. It's broken backwards-compatibility and has a completely new user experience. You won't get this version until you increment the major number in the pubspec.yaml file.
And so, in this case, add this to your package's pubspec.yaml file instead:
dependencies:
state_extended:^8.9.0Turn to this free Medium article for a full overview of the package plus examples:

Copy and paste the code below to get started. Examine the paths specified at the start of every code sequence to determine where these files are to be located.
/// example/lib/main.dartimport'package:example/src/view.dart';
voidmain() =>runApp(MyApp(key:constKey('MyApp')));/// example/src/app/view/my_app.dartimport'package:example/src/view.dart';
import'package:example/src/controller.dart';
classMyAppextendsAppStatefulWidgetMVC {
constMyApp({Key? key}) :super(key: key);
/// This is the App's State object@overrideAppStateMVCcreateState() =>_MyAppState();
}
class_MyAppStateextendsAppStateMVC<MyApp> {
factory_MyAppState() => _this ??=_MyAppState._();
static_MyAppState? _this;
@overrideWidgetbuildApp(BuildContext context) =>MaterialApp(
home:FutureBuilder<bool>(
future:initAsync(),
builder: (context, snapshot) {
//if (snapshot.hasData) {
//if (snapshot.data!) {
/// Key identifies the widget. New key? New widget! /// Demonstrates how to explicitly 're-create' a State objectreturnMyHomePage(key:UniqueKey());
} else {
//returnconstText('Failed to startup');
}
} elseif (snapshot.hasError) {
//returnText('${snapshot.error}');
}
// By default, show a loading spinner.returnconstCenter(child:CircularProgressIndicator());
}),
);
}/// example/src/app/controller/app_controller.dartimport'package:example/src/view.dart';
classAppControllerextendsControllerMVCwithAppControllerMVC {
factoryAppController() => _this ??=AppController._();
AppController._();
staticAppController? _this;
/// Initialize any 'time-consuming' operations at the beginning. /// Initialize asynchronous items essential to the Mobile Applications. /// Typically called within a FutureBuilder() widget.@overrideFuture<bool> initAsync() async {
// Simply wait for 10 seconds at startup./// In production, this is where databases are opened, logins attempted, etc.returnFuture.delayed(constDuration(seconds:10), () {
returntrue;
});
}
/// Supply an 'error handler' routine if something goes wrong /// in the corresponding initAsync() routine. /// Returns true if the error was properly handled.@overrideboolonAsyncError(FlutterErrorDetails details) {
returnfalse;
}
}/// example/src/home/view/my_home_page.dartimport'package:example/src/view.dart';
import'package:example/src/controller.dart';
/// The Home pageclassMyHomePageextendsStatefulWidget {
constMyHomePage({Key? key, this.title ='Flutter Demo'}) :super(key: key);
// Fields in a StatefulWidget should always be "final".finalString title;
@overrideStatecreateState() =>_MyHomePageState();
}
/// This 'MVC version' is a subclass of the State class./// This version is linked to the App's lifecycle using [WidgetsBindingObserver]class_MyHomePageStateextendsStateMVC<MyHomePage> {
/// Let the 'business logic' run in a Controller_MyHomePageState() :super(Controller()) {
/// Acquire a reference to the passed Controller. con = controller asController;
}
lateController con;
@overridevoidinitState() {
/// Look inside the parent function and see it calls /// all it's Controllers if any.super.initState();
/// Retrieve the 'app level' State object appState = rootState!;
/// You're able to retrieve the Controller(s) from other State objects.var con = appState.controller;
con = appState.controllerByType<AppController>();
con = appState.controllerById(con?.keyId);
}
lateAppStateMVC appState;
/// This is 'the View'; the interface of the home page.@overrideWidgetbuild(BuildContext context) =>Scaffold(
appBar:AppBar(
title:Text(widget.title),
),
body:Center(
child:Column(
mainAxisAlignment:MainAxisAlignment.center,
children:<Widget>[
/// Display the App's data object if it has something to displayif (con.dataObject !=null&& con.dataObject isString)
Padding(
padding:constEdgeInsets.all(30),
child:Text(
con.dataObject asString,
key:constKey('greetings'),
style:TextStyle(
color:Colors.red,
fontSize:Theme.of(context).textTheme.headline4!.fontSize,
),
),
),
Text(
'You have pushed the button this many times:',
style:Theme.of(context).textTheme.bodyText2,
),
// Text(// '${con.count}',// style: Theme.of(context).textTheme.headline4,// ),SetState(
builder: (context, dataObject) =>Text(
'${con.count}',
style:Theme.of(context).textTheme.headline4,
),
),
],
),
),
floatingActionButton:FloatingActionButton(
key:constKey('+'),
/// Refresh only the Text widget containing the counter. onPressed: () => con.incrementCounter(),
/// The traditional approach calling the State object's setState() function.// onPressed: () {// setState(con.incrementCounter);// },/// You can have the Controller called the interface (the View).// onPressed: con.onPressed,
child:constIcon(Icons.add),
),
);
/// Supply an error handler for Unit Testing.@overridevoidonError(FlutterErrorDetails details) {
/// Error is now handled.super.onError(details);
}
}/// example/src/home/controller/controller.dartimport'package:example/src/view.dart';
import'package:example/src/model.dart';
classControllerextendsControllerMVC {
factoryController([StateMVC? state]) => _this ??=Controller._(state);
Controller._(StateMVC? state)
: _model =Model(),
super(state);
staticController? _this;
finalModel _model;
/// Note, the count comes from a separate class, _Model.intget count => _model.counter;
// The Controller knows how to 'talk to' the Model and to the View (interface).voidincrementCounter() {
//
_model.incrementCounter();
/// Only calls only 'SetState' widgets /// or widgets that called the inheritWidget(context) functioninheritBuild();
/// Retrieve a particular State object.final homeState =stateOf<MyHomePage>();
/// If working with a particular State object and if divisible by 5if (homeState !=null&& _model.counter %5==0) {
//
dataObject = _model.sayHello();
setState(() {});
}
}
/// Call the State object's setState() function to reflect the change.voidonPressed() =>setState(() => _model.incrementCounter());
}/// example/src/view.dartexport'package:flutter/material.dart'hide StateSetter;
export'package:state_extended/state_extended.dart';
export'package:example/src/app/view/my_app.dart';
export'package:example/src/home/view/my_home_page.dart';
export'package:example/src/home/view/page_01.dart';
export'package:example/src/home/view/page_02.dart';
export'package:example/src/home/view/page_03.dart';
export'package:example/src/home/view/common/build_page.dart';/// example/src/controller.dartexport'package:example/src/app/controller/app_controller.dart';
export'package:example/src/home/controller/controller.dart';
export'package:example/src/home/controller/another_controller.dart';
export'package:example/src/home/controller/yet_another_controller.dart';/// example/src/model.dartexport'package:example/src/home/model/data_source.dart';Further information on the MVC package can be found in the article, ‘MVC in Flutter’
