Repository files navigation

mvc_pattern

codecovCIMediumPub.devGitHub starsLast Commitlikes

Note, mvc_pattern has been rebranded, StateX. This package here is soon deprecated.

StateX

The "Kiss" of Flutter Frameworks

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.

  1. patch - I've made bugfixes
  2. minor - I've introduced new features
  3. 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.0

Documentation

Turn to this free Medium article for a full overview of the package plus examples: FlutterFramework

Example Code

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’online article

About

Flutter Plugin to implement one of many variations of the MVC design pattern.

Resources

Stars

165 stars

Watchers

10 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

mvc_pattern

codecovCIMediumPub.devGitHub starsLast Commitlikes

Note, mvc_pattern has been rebranded, StateX. This package here is soon deprecated.

StateX

The "Kiss" of Flutter Frameworks

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.

  1. patch - I've made bugfixes
  2. minor - I've introduced new features
  3. 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.0

Documentation

Turn to this free Medium article for a full overview of the package plus examples: FlutterFramework

Example Code

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’online article

About

Flutter Plugin to implement one of many variations of the MVC design pattern.

Resources

Stars

165 stars

Watchers

10 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

mvc_pattern

codecovCIMediumPub.devGitHub starsLast Commitlikes

Note, mvc_pattern has been rebranded, StateX. This package here is soon deprecated.

StateX

The "Kiss" of Flutter Frameworks

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.

  1. patch - I've made bugfixes
  2. minor - I've introduced new features
  3. 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.0

Documentation

Turn to this free Medium article for a full overview of the package plus examples: FlutterFramework

Example Code

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’online article

About

Flutter Plugin to implement one of many variations of the MVC design pattern.

Resources

Stars

165 stars

Watchers

10 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

mvc_pattern

codecovCIMediumPub.devGitHub starsLast Commitlikes

Note, mvc_pattern has been rebranded, StateX. This package here is soon deprecated.

StateX

The "Kiss" of Flutter Frameworks

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.

  1. patch - I've made bugfixes
  2. minor - I've introduced new features
  3. 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.0

Documentation

Turn to this free Medium article for a full overview of the package plus examples: FlutterFramework

Example Code

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’online article

About

Flutter Plugin to implement one of many variations of the MVC design pattern.

Resources

Stars

165 stars

Watchers

10 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

mvc_pattern

codecovCIMediumPub.devGitHub starsLast Commitlikes

Note, mvc_pattern has been rebranded, StateX. This package here is soon deprecated.

StateX

The "Kiss" of Flutter Frameworks

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.

  1. patch - I've made bugfixes
  2. minor - I've introduced new features
  3. 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.0

Documentation

Turn to this free Medium article for a full overview of the package plus examples: FlutterFramework

Example Code

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’online article

About

Flutter Plugin to implement one of many variations of the MVC design pattern.

Resources

Stars

165 stars

Watchers

10 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

mvc_pattern

codecovCIMediumPub.devGitHub starsLast Commitlikes

Note, mvc_pattern has been rebranded, StateX. This package here is soon deprecated.

StateX

The "Kiss" of Flutter Frameworks

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.

  1. patch - I've made bugfixes
  2. minor - I've introduced new features
  3. 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.0

Documentation

Turn to this free Medium article for a full overview of the package plus examples: FlutterFramework

Example Code

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’online article

About

Flutter Plugin to implement one of many variations of the MVC design pattern.

Resources

Stars

165 stars

Watchers

10 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

mvc_pattern

codecovCIMediumPub.devGitHub starsLast Commitlikes

Note, mvc_pattern has been rebranded, StateX. This package here is soon deprecated.

StateX

The "Kiss" of Flutter Frameworks

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.

  1. patch - I've made bugfixes
  2. minor - I've introduced new features
  3. 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.0

Documentation

Turn to this free Medium article for a full overview of the package plus examples: FlutterFramework

Example Code

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’online article

About

Flutter Plugin to implement one of many variations of the MVC design pattern.

Resources

Stars

165 stars

Watchers

10 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

mvc_pattern

codecovCIMediumPub.devGitHub starsLast Commitlikes

Note, mvc_pattern has been rebranded, StateX. This package here is soon deprecated.

StateX

The "Kiss" of Flutter Frameworks

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.

  1. patch - I've made bugfixes
  2. minor - I've introduced new features
  3. 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.0

Documentation

Turn to this free Medium article for a full overview of the package plus examples: FlutterFramework

Example Code

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’online article

About

Flutter Plugin to implement one of many variations of the MVC design pattern.

Resources

Stars

165 stars

Watchers

10 watching

Forks

Releases

Packages

Used by

Contributors

Languages