Repository files navigation

MVC Application

Build StatusMediumPub.devGitHub starsLast Commit

A Flutter Framework using the MVC Design Pattern

mvc_application Allows for easier and, dare I say, faster development and better maintenability. No 're-inventing of the wheel' using already built-in capabilities and features offered by Flutter itself. Accommodating and Integrated features:

  • Error Handling
  • System Preferences
  • App Notifications
  • A Better Menu Bar
  • Device Event Handling
  • Date picker
  • App Color picker
  • Dialog Boxes
  • Customizable Bottom Bar
  • Loading Screen
  • Time Zones

Installing

I don't like the version number suggested in the 'Installing' page. Instead, always go up to the 'major' semantic version number when installing this library package. This means always 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. Example, to install version 7.9.2, use 7.0.0. Thus, the bug fix, 7.9.2, will be installed the next time you 'upgrade' the dependencies.

  1. patch - bugfixes
  2. minor - Introduced new features
  3. major - 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:

dependencies:
mvc_application:^8.9.0

For more information on version numbers: The importance of semantic versioning.

Note, in fact, this package serves as a 'wrapper' for the core MVC package:

MVC Pattern

Pub.devGitHub starsLast Commit

Usage

Like many other design patterns, MVC separates three common 'areas of concern' found in computer programs. Specifically, there's the separation of the program's logic from its inteface and from its data. The many design patterns that have come into vogue since MVC's inception in the early 1970's are decendants of MVC.

Note, computer platforms themselves have come full circle in the last 40 years. From mainframes to desktop computers in the first twenty years, and then website applications to mobile apps in the last twenty years. A computer now fits in the palm of your hand, and because of this, MVC again fits as the design pattern and framework for the apps it runs. mvcTypes

Implementing the MVC framework using two common example apps:

The Counter App
import'package:mvc_application/view.dart';
import'package:mvc_application/controller.dart';
voidmain() =>runApp(MyApp());
classMyAppextendsAppStatefulWidget {
MyApp({Key? key}) :super(key: key);
@overrideAppStatecreateState() =>View();
}
classViewextendsAppState {
View()
:super(
title:'Flutter Demo',
home:constMyHomePage(),
debugShowCheckedModeBanner:false,
theme:ThemeData(
primarySwatch:Colors.blue,
),
);
}
classMyHomePageextendsStatefulWidget {
constMyHomePage({Key? key, this.title ='Flutter Demo Home Page'})
:super(key: key);
// Fields in a StatefulWidget should always be "final".finalString title;
@overrideStatecreateState() =>_MyHomePageState();
}
class_MyHomePageStateextendsStateMVC<MyHomePage> {
_MyHomePageState() :super(Controller()) {
con = controller asController;
}
lateController con;
@overrideWidgetbuild(BuildContext context) =>Scaffold(
appBar:AppBar(
title:Text(widget.title),
),
body:Center(
child:Column(
mainAxisAlignment:MainAxisAlignment.center,
children:<Widget>[
constText('You have pushed the button this many times:'),
Text(
'${con.counter}',
style:Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton:FloatingActionButton(
/// Try this alternative approach. /// The Controller merely mimics the Flutter's API// onPressed: con.onPressed,
onPressed: () =>setState(con.incrementCounter),
tooltip:'Increment',
child:constIcon(Icons.add),
),
);
}
classControllerextendsControllerMVC {
factoryController() => _this ??=Controller._();
Controller._()
: model =_Model(),
super();
staticController? _this;
final_Model model;
/// You're free to mimic Flutter's own API /// The Controller is able to talk to the View (the State object)voidonPressed() =>setState(() => model._incrementCounter());
intget counter => model.integer;
/// The Controller knows how to 'talk to' the Model.voidincrementCounter() => model._incrementCounter();
}
class_Model {
intget integer => _integer;
int _integer =0;
int_incrementCounter() =>++_integer;
}

Name Generator App

import'package:english_words/english_words.dart'show generateWordPairs;
import'package:flutter/material.dart'hide runApp;
import'package:mvc_application/view.dart'show AppMVC, AppState, Colors, runApp, StateMVC;
import'package:mvc_application/controller.dart'show ControllerMVC;
voidmain() =>runApp(NameApp());
classNameAppextendsAppMVC {
NameApp({Key? key}) :super(key: key);
@overrideAppStatecreateState() =>MyApp();
}
classMyAppextendsAppState {
factoryMyApp() => _this ??=MyApp._();
MyApp._()
:super(
title:'Startup Name Generator',
home:constRandomWords(),
theme:ThemeData(
primaryColor:Colors.white,
),
debugShowCheckedModeBanner:false,
);
staticMyApp? _this;
}
classRandomWordsextendsStatefulWidget {
constRandomWords({Key? key}) :super(key: key);
@overrideStatecreateState() =>_RandomWordsState();
}
class_RandomWordsStateextendsStateMVC<RandomWords> {
_RandomWordsState() :super(_Controller()) {
con = controller as_Controller;
}
late_Controller con;
finalTextStyle _biggerFont =constTextStyle(fontSize:18.0);
@overrideWidgetbuild(BuildContext context) {
returnScaffold(
appBar:AppBar(
title:constText('Startup Name Generator'),
actions:<Widget>[
IconButton(
icon:constIcon(Icons.list),
onPressed: () {
pushSaved(context);
}),
],
),
body:_buildSuggestions(),
);
}
Widget_buildSuggestions() {
returnListView.builder(
padding:constEdgeInsets.all(16.0),
itemBuilder: (context, i) {
// Add a one-pixel-high divider widget before each row in theListView.if (i.isOdd) returnconstDivider();
final index = i ~/2;
// If you've reached the end of the available word pairings...if (index >= con.length) {
// ...then generate 10 more and add them to the suggestions list.
con.addAll(10);
}
returnbuildRow(index);
},
);
}
voidpushSaved(BuildContext context) {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (BuildContext context) {
finalIterable<ListTile> tiles =this.tiles;
List<Widget> divided;
if (tiles.isEmpty) {
divided = [];
} else {
divided =ListTile.divideTiles(
context: context,
tiles: tiles,
).toList();
}
returnScaffold(
appBar:AppBar(
title:constText('Saved Suggestions'),
),
body:ListView(children: divided),
);
},
),
);
}
WidgetbuildRow(int? index) {
if (index ==null|| index <0) index =0;
String something = con.something(index);
final alreadySaved = con.contains(something);
returnListTile(
title:Text(
something,
style: _biggerFont,
),
trailing:Icon(
alreadySaved ?Icons.favorite :Icons.favorite_border,
color: alreadySaved ?Colors.red :null,
),
onTap: () {
setState(() {
con.somethingHappens(something);
});
},
);
}
Iterable<ListTile> get tiles => con.mapHappens(
(String something) {
returnListTile(
title:Text(
something,
style: _biggerFont,
),
);
},
);
}
class_ControllerextendsControllerMVC {
// Supply only one instance of this Controller class.factory_Controller() => _this ??=_Controller._();
static_Controller? _this;
_Controller._() {
model =_Model();
}
late_Model model;
intget length => model.length;
voidaddAll(int count) => model.addAll(count);
Stringsomething(int index) => model.wordPair(index);
boolcontains(String something) => model.contains(something);
voidsomethingHappens(String something) => model.save(something);
Iterable<ListTile> mapHappens<ListTile>(ListTileFunction(String v) f) =>
model.saved(f);
}
class_Model {
finalList<String> _suggestions = [];
intget length => _suggestions.length;
StringwordPair(int? index) {
if (index ==null|| index <0) index =0;
return _suggestions[index];
}
boolcontains(String? pair) {
if (pair ==null|| pair.isEmpty) returnfalse;
return _saved.contains(pair);
}
finalSet<String> _saved = {};
voidsave(String? pair) {
if (pair ==null|| pair.isEmpty) return;
final alreadySaved =contains(pair);
if (alreadySaved) {
_saved.remove(pair);
} else {
_saved.add(pair);
}
}
Iterable<ListTile> saved<ListTile>(ListTileFunction(String v) f) =>
_saved.map(f);
Iterable<String> wordPairs([int count =10]) =>_makeWordPairs(count);
voidaddAll(int count) => _suggestions.addAll(wordPairs(count));
}
Iterable<String> _makeWordPairs(int count) =>generateWordPairs().take(count).map((pair) => pair.asPascalCase);

Additional Documentation

Please begin with the article, ‘Flutter + MVC at Last!’online article

Follow up with Bazaar in MVC and Shrine in MVC and Weather App in "mvc pattern"bizzarMVCshrineMVCMVCWeatherApp

Optionally, there is the 3-part series beginning with, MVC in FlutterMVCFlutter

Further articles include, A Design Pattern for Flutter. designpattern

Other Dart Packages

packages Other Dart packages from the author can also be found at Pub.dev

About

Application Framework conveying the MVC design pattern.

Resources

Stars

27 stars

Watchers

1 watching

Forks

Releases

Packages

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 Application

Build StatusMediumPub.devGitHub starsLast Commit

A Flutter Framework using the MVC Design Pattern

mvc_application Allows for easier and, dare I say, faster development and better maintenability. No 're-inventing of the wheel' using already built-in capabilities and features offered by Flutter itself. Accommodating and Integrated features:

  • Error Handling
  • System Preferences
  • App Notifications
  • A Better Menu Bar
  • Device Event Handling
  • Date picker
  • App Color picker
  • Dialog Boxes
  • Customizable Bottom Bar
  • Loading Screen
  • Time Zones

Installing

I don't like the version number suggested in the 'Installing' page. Instead, always go up to the 'major' semantic version number when installing this library package. This means always 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. Example, to install version 7.9.2, use 7.0.0. Thus, the bug fix, 7.9.2, will be installed the next time you 'upgrade' the dependencies.

  1. patch - bugfixes
  2. minor - Introduced new features
  3. major - 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:

dependencies:
mvc_application:^8.9.0

For more information on version numbers: The importance of semantic versioning.

Note, in fact, this package serves as a 'wrapper' for the core MVC package:

MVC Pattern

Pub.devGitHub starsLast Commit

Usage

Like many other design patterns, MVC separates three common 'areas of concern' found in computer programs. Specifically, there's the separation of the program's logic from its inteface and from its data. The many design patterns that have come into vogue since MVC's inception in the early 1970's are decendants of MVC.

Note, computer platforms themselves have come full circle in the last 40 years. From mainframes to desktop computers in the first twenty years, and then website applications to mobile apps in the last twenty years. A computer now fits in the palm of your hand, and because of this, MVC again fits as the design pattern and framework for the apps it runs. mvcTypes

Implementing the MVC framework using two common example apps:

The Counter App
import'package:mvc_application/view.dart';
import'package:mvc_application/controller.dart';
voidmain() =>runApp(MyApp());
classMyAppextendsAppStatefulWidget {
MyApp({Key? key}) :super(key: key);
@overrideAppStatecreateState() =>View();
}
classViewextendsAppState {
View()
:super(
title:'Flutter Demo',
home:constMyHomePage(),
debugShowCheckedModeBanner:false,
theme:ThemeData(
primarySwatch:Colors.blue,
),
);
}
classMyHomePageextendsStatefulWidget {
constMyHomePage({Key? key, this.title ='Flutter Demo Home Page'})
:super(key: key);
// Fields in a StatefulWidget should always be "final".finalString title;
@overrideStatecreateState() =>_MyHomePageState();
}
class_MyHomePageStateextendsStateMVC<MyHomePage> {
_MyHomePageState() :super(Controller()) {
con = controller asController;
}
lateController con;
@overrideWidgetbuild(BuildContext context) =>Scaffold(
appBar:AppBar(
title:Text(widget.title),
),
body:Center(
child:Column(
mainAxisAlignment:MainAxisAlignment.center,
children:<Widget>[
constText('You have pushed the button this many times:'),
Text(
'${con.counter}',
style:Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton:FloatingActionButton(
/// Try this alternative approach. /// The Controller merely mimics the Flutter's API// onPressed: con.onPressed,
onPressed: () =>setState(con.incrementCounter),
tooltip:'Increment',
child:constIcon(Icons.add),
),
);
}
classControllerextendsControllerMVC {
factoryController() => _this ??=Controller._();
Controller._()
: model =_Model(),
super();
staticController? _this;
final_Model model;
/// You're free to mimic Flutter's own API /// The Controller is able to talk to the View (the State object)voidonPressed() =>setState(() => model._incrementCounter());
intget counter => model.integer;
/// The Controller knows how to 'talk to' the Model.voidincrementCounter() => model._incrementCounter();
}
class_Model {
intget integer => _integer;
int _integer =0;
int_incrementCounter() =>++_integer;
}

Name Generator App

import'package:english_words/english_words.dart'show generateWordPairs;
import'package:flutter/material.dart'hide runApp;
import'package:mvc_application/view.dart'show AppMVC, AppState, Colors, runApp, StateMVC;
import'package:mvc_application/controller.dart'show ControllerMVC;
voidmain() =>runApp(NameApp());
classNameAppextendsAppMVC {
NameApp({Key? key}) :super(key: key);
@overrideAppStatecreateState() =>MyApp();
}
classMyAppextendsAppState {
factoryMyApp() => _this ??=MyApp._();
MyApp._()
:super(
title:'Startup Name Generator',
home:constRandomWords(),
theme:ThemeData(
primaryColor:Colors.white,
),
debugShowCheckedModeBanner:false,
);
staticMyApp? _this;
}
classRandomWordsextendsStatefulWidget {
constRandomWords({Key? key}) :super(key: key);
@overrideStatecreateState() =>_RandomWordsState();
}
class_RandomWordsStateextendsStateMVC<RandomWords> {
_RandomWordsState() :super(_Controller()) {
con = controller as_Controller;
}
late_Controller con;
finalTextStyle _biggerFont =constTextStyle(fontSize:18.0);
@overrideWidgetbuild(BuildContext context) {
returnScaffold(
appBar:AppBar(
title:constText('Startup Name Generator'),
actions:<Widget>[
IconButton(
icon:constIcon(Icons.list),
onPressed: () {
pushSaved(context);
}),
],
),
body:_buildSuggestions(),
);
}
Widget_buildSuggestions() {
returnListView.builder(
padding:constEdgeInsets.all(16.0),
itemBuilder: (context, i) {
// Add a one-pixel-high divider widget before each row in theListView.if (i.isOdd) returnconstDivider();
final index = i ~/2;
// If you've reached the end of the available word pairings...if (index >= con.length) {
// ...then generate 10 more and add them to the suggestions list.
con.addAll(10);
}
returnbuildRow(index);
},
);
}
voidpushSaved(BuildContext context) {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (BuildContext context) {
finalIterable<ListTile> tiles =this.tiles;
List<Widget> divided;
if (tiles.isEmpty) {
divided = [];
} else {
divided =ListTile.divideTiles(
context: context,
tiles: tiles,
).toList();
}
returnScaffold(
appBar:AppBar(
title:constText('Saved Suggestions'),
),
body:ListView(children: divided),
);
},
),
);
}
WidgetbuildRow(int? index) {
if (index ==null|| index <0) index =0;
String something = con.something(index);
final alreadySaved = con.contains(something);
returnListTile(
title:Text(
something,
style: _biggerFont,
),
trailing:Icon(
alreadySaved ?Icons.favorite :Icons.favorite_border,
color: alreadySaved ?Colors.red :null,
),
onTap: () {
setState(() {
con.somethingHappens(something);
});
},
);
}
Iterable<ListTile> get tiles => con.mapHappens(
(String something) {
returnListTile(
title:Text(
something,
style: _biggerFont,
),
);
},
);
}
class_ControllerextendsControllerMVC {
// Supply only one instance of this Controller class.factory_Controller() => _this ??=_Controller._();
static_Controller? _this;
_Controller._() {
model =_Model();
}
late_Model model;
intget length => model.length;
voidaddAll(int count) => model.addAll(count);
Stringsomething(int index) => model.wordPair(index);
boolcontains(String something) => model.contains(something);
voidsomethingHappens(String something) => model.save(something);
Iterable<ListTile> mapHappens<ListTile>(ListTileFunction(String v) f) =>
model.saved(f);
}
class_Model {
finalList<String> _suggestions = [];
intget length => _suggestions.length;
StringwordPair(int? index) {
if (index ==null|| index <0) index =0;
return _suggestions[index];
}
boolcontains(String? pair) {
if (pair ==null|| pair.isEmpty) returnfalse;
return _saved.contains(pair);
}
finalSet<String> _saved = {};
voidsave(String? pair) {
if (pair ==null|| pair.isEmpty) return;
final alreadySaved =contains(pair);
if (alreadySaved) {
_saved.remove(pair);
} else {
_saved.add(pair);
}
}
Iterable<ListTile> saved<ListTile>(ListTileFunction(String v) f) =>
_saved.map(f);
Iterable<String> wordPairs([int count =10]) =>_makeWordPairs(count);
voidaddAll(int count) => _suggestions.addAll(wordPairs(count));
}
Iterable<String> _makeWordPairs(int count) =>generateWordPairs().take(count).map((pair) => pair.asPascalCase);

Additional Documentation

Please begin with the article, ‘Flutter + MVC at Last!’online article

Follow up with Bazaar in MVC and Shrine in MVC and Weather App in "mvc pattern"bizzarMVCshrineMVCMVCWeatherApp

Optionally, there is the 3-part series beginning with, MVC in FlutterMVCFlutter

Further articles include, A Design Pattern for Flutter. designpattern

Other Dart Packages

packages Other Dart packages from the author can also be found at Pub.dev

About

Application Framework conveying the MVC design pattern.

Resources

Stars

27 stars

Watchers

1 watching

Forks

Releases

Packages

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 Application

Build StatusMediumPub.devGitHub starsLast Commit

A Flutter Framework using the MVC Design Pattern

mvc_application Allows for easier and, dare I say, faster development and better maintenability. No 're-inventing of the wheel' using already built-in capabilities and features offered by Flutter itself. Accommodating and Integrated features:

  • Error Handling
  • System Preferences
  • App Notifications
  • A Better Menu Bar
  • Device Event Handling
  • Date picker
  • App Color picker
  • Dialog Boxes
  • Customizable Bottom Bar
  • Loading Screen
  • Time Zones

Installing

I don't like the version number suggested in the 'Installing' page. Instead, always go up to the 'major' semantic version number when installing this library package. This means always 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. Example, to install version 7.9.2, use 7.0.0. Thus, the bug fix, 7.9.2, will be installed the next time you 'upgrade' the dependencies.

  1. patch - bugfixes
  2. minor - Introduced new features
  3. major - 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:

dependencies:
mvc_application:^8.9.0

For more information on version numbers: The importance of semantic versioning.

Note, in fact, this package serves as a 'wrapper' for the core MVC package:

MVC Pattern

Pub.devGitHub starsLast Commit

Usage

Like many other design patterns, MVC separates three common 'areas of concern' found in computer programs. Specifically, there's the separation of the program's logic from its inteface and from its data. The many design patterns that have come into vogue since MVC's inception in the early 1970's are decendants of MVC.

Note, computer platforms themselves have come full circle in the last 40 years. From mainframes to desktop computers in the first twenty years, and then website applications to mobile apps in the last twenty years. A computer now fits in the palm of your hand, and because of this, MVC again fits as the design pattern and framework for the apps it runs. mvcTypes

Implementing the MVC framework using two common example apps:

The Counter App
import'package:mvc_application/view.dart';
import'package:mvc_application/controller.dart';
voidmain() =>runApp(MyApp());
classMyAppextendsAppStatefulWidget {
MyApp({Key? key}) :super(key: key);
@overrideAppStatecreateState() =>View();
}
classViewextendsAppState {
View()
:super(
title:'Flutter Demo',
home:constMyHomePage(),
debugShowCheckedModeBanner:false,
theme:ThemeData(
primarySwatch:Colors.blue,
),
);
}
classMyHomePageextendsStatefulWidget {
constMyHomePage({Key? key, this.title ='Flutter Demo Home Page'})
:super(key: key);
// Fields in a StatefulWidget should always be "final".finalString title;
@overrideStatecreateState() =>_MyHomePageState();
}
class_MyHomePageStateextendsStateMVC<MyHomePage> {
_MyHomePageState() :super(Controller()) {
con = controller asController;
}
lateController con;
@overrideWidgetbuild(BuildContext context) =>Scaffold(
appBar:AppBar(
title:Text(widget.title),
),
body:Center(
child:Column(
mainAxisAlignment:MainAxisAlignment.center,
children:<Widget>[
constText('You have pushed the button this many times:'),
Text(
'${con.counter}',
style:Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton:FloatingActionButton(
/// Try this alternative approach. /// The Controller merely mimics the Flutter's API// onPressed: con.onPressed,
onPressed: () =>setState(con.incrementCounter),
tooltip:'Increment',
child:constIcon(Icons.add),
),
);
}
classControllerextendsControllerMVC {
factoryController() => _this ??=Controller._();
Controller._()
: model =_Model(),
super();
staticController? _this;
final_Model model;
/// You're free to mimic Flutter's own API /// The Controller is able to talk to the View (the State object)voidonPressed() =>setState(() => model._incrementCounter());
intget counter => model.integer;
/// The Controller knows how to 'talk to' the Model.voidincrementCounter() => model._incrementCounter();
}
class_Model {
intget integer => _integer;
int _integer =0;
int_incrementCounter() =>++_integer;
}

Name Generator App

import'package:english_words/english_words.dart'show generateWordPairs;
import'package:flutter/material.dart'hide runApp;
import'package:mvc_application/view.dart'show AppMVC, AppState, Colors, runApp, StateMVC;
import'package:mvc_application/controller.dart'show ControllerMVC;
voidmain() =>runApp(NameApp());
classNameAppextendsAppMVC {
NameApp({Key? key}) :super(key: key);
@overrideAppStatecreateState() =>MyApp();
}
classMyAppextendsAppState {
factoryMyApp() => _this ??=MyApp._();
MyApp._()
:super(
title:'Startup Name Generator',
home:constRandomWords(),
theme:ThemeData(
primaryColor:Colors.white,
),
debugShowCheckedModeBanner:false,
);
staticMyApp? _this;
}
classRandomWordsextendsStatefulWidget {
constRandomWords({Key? key}) :super(key: key);
@overrideStatecreateState() =>_RandomWordsState();
}
class_RandomWordsStateextendsStateMVC<RandomWords> {
_RandomWordsState() :super(_Controller()) {
con = controller as_Controller;
}
late_Controller con;
finalTextStyle _biggerFont =constTextStyle(fontSize:18.0);
@overrideWidgetbuild(BuildContext context) {
returnScaffold(
appBar:AppBar(
title:constText('Startup Name Generator'),
actions:<Widget>[
IconButton(
icon:constIcon(Icons.list),
onPressed: () {
pushSaved(context);
}),
],
),
body:_buildSuggestions(),
);
}
Widget_buildSuggestions() {
returnListView.builder(
padding:constEdgeInsets.all(16.0),
itemBuilder: (context, i) {
// Add a one-pixel-high divider widget before each row in theListView.if (i.isOdd) returnconstDivider();
final index = i ~/2;
// If you've reached the end of the available word pairings...if (index >= con.length) {
// ...then generate 10 more and add them to the suggestions list.
con.addAll(10);
}
returnbuildRow(index);
},
);
}
voidpushSaved(BuildContext context) {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (BuildContext context) {
finalIterable<ListTile> tiles =this.tiles;
List<Widget> divided;
if (tiles.isEmpty) {
divided = [];
} else {
divided =ListTile.divideTiles(
context: context,
tiles: tiles,
).toList();
}
returnScaffold(
appBar:AppBar(
title:constText('Saved Suggestions'),
),
body:ListView(children: divided),
);
},
),
);
}
WidgetbuildRow(int? index) {
if (index ==null|| index <0) index =0;
String something = con.something(index);
final alreadySaved = con.contains(something);
returnListTile(
title:Text(
something,
style: _biggerFont,
),
trailing:Icon(
alreadySaved ?Icons.favorite :Icons.favorite_border,
color: alreadySaved ?Colors.red :null,
),
onTap: () {
setState(() {
con.somethingHappens(something);
});
},
);
}
Iterable<ListTile> get tiles => con.mapHappens(
(String something) {
returnListTile(
title:Text(
something,
style: _biggerFont,
),
);
},
);
}
class_ControllerextendsControllerMVC {
// Supply only one instance of this Controller class.factory_Controller() => _this ??=_Controller._();
static_Controller? _this;
_Controller._() {
model =_Model();
}
late_Model model;
intget length => model.length;
voidaddAll(int count) => model.addAll(count);
Stringsomething(int index) => model.wordPair(index);
boolcontains(String something) => model.contains(something);
voidsomethingHappens(String something) => model.save(something);
Iterable<ListTile> mapHappens<ListTile>(ListTileFunction(String v) f) =>
model.saved(f);
}
class_Model {
finalList<String> _suggestions = [];
intget length => _suggestions.length;
StringwordPair(int? index) {
if (index ==null|| index <0) index =0;
return _suggestions[index];
}
boolcontains(String? pair) {
if (pair ==null|| pair.isEmpty) returnfalse;
return _saved.contains(pair);
}
finalSet<String> _saved = {};
voidsave(String? pair) {
if (pair ==null|| pair.isEmpty) return;
final alreadySaved =contains(pair);
if (alreadySaved) {
_saved.remove(pair);
} else {
_saved.add(pair);
}
}
Iterable<ListTile> saved<ListTile>(ListTileFunction(String v) f) =>
_saved.map(f);
Iterable<String> wordPairs([int count =10]) =>_makeWordPairs(count);
voidaddAll(int count) => _suggestions.addAll(wordPairs(count));
}
Iterable<String> _makeWordPairs(int count) =>generateWordPairs().take(count).map((pair) => pair.asPascalCase);

Additional Documentation

Please begin with the article, ‘Flutter + MVC at Last!’online article

Follow up with Bazaar in MVC and Shrine in MVC and Weather App in "mvc pattern"bizzarMVCshrineMVCMVCWeatherApp

Optionally, there is the 3-part series beginning with, MVC in FlutterMVCFlutter

Further articles include, A Design Pattern for Flutter. designpattern

Other Dart Packages

packages Other Dart packages from the author can also be found at Pub.dev

About

Application Framework conveying the MVC design pattern.

Resources

Stars

27 stars

Watchers

1 watching

Forks

Releases

Packages

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 Application

Build StatusMediumPub.devGitHub starsLast Commit

A Flutter Framework using the MVC Design Pattern

mvc_application Allows for easier and, dare I say, faster development and better maintenability. No 're-inventing of the wheel' using already built-in capabilities and features offered by Flutter itself. Accommodating and Integrated features:

  • Error Handling
  • System Preferences
  • App Notifications
  • A Better Menu Bar
  • Device Event Handling
  • Date picker
  • App Color picker
  • Dialog Boxes
  • Customizable Bottom Bar
  • Loading Screen
  • Time Zones

Installing

I don't like the version number suggested in the 'Installing' page. Instead, always go up to the 'major' semantic version number when installing this library package. This means always 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. Example, to install version 7.9.2, use 7.0.0. Thus, the bug fix, 7.9.2, will be installed the next time you 'upgrade' the dependencies.

  1. patch - bugfixes
  2. minor - Introduced new features
  3. major - 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:

dependencies:
mvc_application:^8.9.0

For more information on version numbers: The importance of semantic versioning.

Note, in fact, this package serves as a 'wrapper' for the core MVC package:

MVC Pattern

Pub.devGitHub starsLast Commit

Usage

Like many other design patterns, MVC separates three common 'areas of concern' found in computer programs. Specifically, there's the separation of the program's logic from its inteface and from its data. The many design patterns that have come into vogue since MVC's inception in the early 1970's are decendants of MVC.

Note, computer platforms themselves have come full circle in the last 40 years. From mainframes to desktop computers in the first twenty years, and then website applications to mobile apps in the last twenty years. A computer now fits in the palm of your hand, and because of this, MVC again fits as the design pattern and framework for the apps it runs. mvcTypes

Implementing the MVC framework using two common example apps:

The Counter App
import'package:mvc_application/view.dart';
import'package:mvc_application/controller.dart';
voidmain() =>runApp(MyApp());
classMyAppextendsAppStatefulWidget {
MyApp({Key? key}) :super(key: key);
@overrideAppStatecreateState() =>View();
}
classViewextendsAppState {
View()
:super(
title:'Flutter Demo',
home:constMyHomePage(),
debugShowCheckedModeBanner:false,
theme:ThemeData(
primarySwatch:Colors.blue,
),
);
}
classMyHomePageextendsStatefulWidget {
constMyHomePage({Key? key, this.title ='Flutter Demo Home Page'})
:super(key: key);
// Fields in a StatefulWidget should always be "final".finalString title;
@overrideStatecreateState() =>_MyHomePageState();
}
class_MyHomePageStateextendsStateMVC<MyHomePage> {
_MyHomePageState() :super(Controller()) {
con = controller asController;
}
lateController con;
@overrideWidgetbuild(BuildContext context) =>Scaffold(
appBar:AppBar(
title:Text(widget.title),
),
body:Center(
child:Column(
mainAxisAlignment:MainAxisAlignment.center,
children:<Widget>[
constText('You have pushed the button this many times:'),
Text(
'${con.counter}',
style:Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton:FloatingActionButton(
/// Try this alternative approach. /// The Controller merely mimics the Flutter's API// onPressed: con.onPressed,
onPressed: () =>setState(con.incrementCounter),
tooltip:'Increment',
child:constIcon(Icons.add),
),
);
}
classControllerextendsControllerMVC {
factoryController() => _this ??=Controller._();
Controller._()
: model =_Model(),
super();
staticController? _this;
final_Model model;
/// You're free to mimic Flutter's own API /// The Controller is able to talk to the View (the State object)voidonPressed() =>setState(() => model._incrementCounter());
intget counter => model.integer;
/// The Controller knows how to 'talk to' the Model.voidincrementCounter() => model._incrementCounter();
}
class_Model {
intget integer => _integer;
int _integer =0;
int_incrementCounter() =>++_integer;
}

Name Generator App

import'package:english_words/english_words.dart'show generateWordPairs;
import'package:flutter/material.dart'hide runApp;
import'package:mvc_application/view.dart'show AppMVC, AppState, Colors, runApp, StateMVC;
import'package:mvc_application/controller.dart'show ControllerMVC;
voidmain() =>runApp(NameApp());
classNameAppextendsAppMVC {
NameApp({Key? key}) :super(key: key);
@overrideAppStatecreateState() =>MyApp();
}
classMyAppextendsAppState {
factoryMyApp() => _this ??=MyApp._();
MyApp._()
:super(
title:'Startup Name Generator',
home:constRandomWords(),
theme:ThemeData(
primaryColor:Colors.white,
),
debugShowCheckedModeBanner:false,
);
staticMyApp? _this;
}
classRandomWordsextendsStatefulWidget {
constRandomWords({Key? key}) :super(key: key);
@overrideStatecreateState() =>_RandomWordsState();
}
class_RandomWordsStateextendsStateMVC<RandomWords> {
_RandomWordsState() :super(_Controller()) {
con = controller as_Controller;
}
late_Controller con;
finalTextStyle _biggerFont =constTextStyle(fontSize:18.0);
@overrideWidgetbuild(BuildContext context) {
returnScaffold(
appBar:AppBar(
title:constText('Startup Name Generator'),
actions:<Widget>[
IconButton(
icon:constIcon(Icons.list),
onPressed: () {
pushSaved(context);
}),
],
),
body:_buildSuggestions(),
);
}
Widget_buildSuggestions() {
returnListView.builder(
padding:constEdgeInsets.all(16.0),
itemBuilder: (context, i) {
// Add a one-pixel-high divider widget before each row in theListView.if (i.isOdd) returnconstDivider();
final index = i ~/2;
// If you've reached the end of the available word pairings...if (index >= con.length) {
// ...then generate 10 more and add them to the suggestions list.
con.addAll(10);
}
returnbuildRow(index);
},
);
}
voidpushSaved(BuildContext context) {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (BuildContext context) {
finalIterable<ListTile> tiles =this.tiles;
List<Widget> divided;
if (tiles.isEmpty) {
divided = [];
} else {
divided =ListTile.divideTiles(
context: context,
tiles: tiles,
).toList();
}
returnScaffold(
appBar:AppBar(
title:constText('Saved Suggestions'),
),
body:ListView(children: divided),
);
},
),
);
}
WidgetbuildRow(int? index) {
if (index ==null|| index <0) index =0;
String something = con.something(index);
final alreadySaved = con.contains(something);
returnListTile(
title:Text(
something,
style: _biggerFont,
),
trailing:Icon(
alreadySaved ?Icons.favorite :Icons.favorite_border,
color: alreadySaved ?Colors.red :null,
),
onTap: () {
setState(() {
con.somethingHappens(something);
});
},
);
}
Iterable<ListTile> get tiles => con.mapHappens(
(String something) {
returnListTile(
title:Text(
something,
style: _biggerFont,
),
);
},
);
}
class_ControllerextendsControllerMVC {
// Supply only one instance of this Controller class.factory_Controller() => _this ??=_Controller._();
static_Controller? _this;
_Controller._() {
model =_Model();
}
late_Model model;
intget length => model.length;
voidaddAll(int count) => model.addAll(count);
Stringsomething(int index) => model.wordPair(index);
boolcontains(String something) => model.contains(something);
voidsomethingHappens(String something) => model.save(something);
Iterable<ListTile> mapHappens<ListTile>(ListTileFunction(String v) f) =>
model.saved(f);
}
class_Model {
finalList<String> _suggestions = [];
intget length => _suggestions.length;
StringwordPair(int? index) {
if (index ==null|| index <0) index =0;
return _suggestions[index];
}
boolcontains(String? pair) {
if (pair ==null|| pair.isEmpty) returnfalse;
return _saved.contains(pair);
}
finalSet<String> _saved = {};
voidsave(String? pair) {
if (pair ==null|| pair.isEmpty) return;
final alreadySaved =contains(pair);
if (alreadySaved) {
_saved.remove(pair);
} else {
_saved.add(pair);
}
}
Iterable<ListTile> saved<ListTile>(ListTileFunction(String v) f) =>
_saved.map(f);
Iterable<String> wordPairs([int count =10]) =>_makeWordPairs(count);
voidaddAll(int count) => _suggestions.addAll(wordPairs(count));
}
Iterable<String> _makeWordPairs(int count) =>generateWordPairs().take(count).map((pair) => pair.asPascalCase);

Additional Documentation

Please begin with the article, ‘Flutter + MVC at Last!’online article

Follow up with Bazaar in MVC and Shrine in MVC and Weather App in "mvc pattern"bizzarMVCshrineMVCMVCWeatherApp

Optionally, there is the 3-part series beginning with, MVC in FlutterMVCFlutter

Further articles include, A Design Pattern for Flutter. designpattern

Other Dart Packages

packages Other Dart packages from the author can also be found at Pub.dev

About

Application Framework conveying the MVC design pattern.

Resources

Stars

27 stars

Watchers

1 watching

Forks

Releases

Packages

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 Application

Build StatusMediumPub.devGitHub starsLast Commit

A Flutter Framework using the MVC Design Pattern

mvc_application Allows for easier and, dare I say, faster development and better maintenability. No 're-inventing of the wheel' using already built-in capabilities and features offered by Flutter itself. Accommodating and Integrated features:

  • Error Handling
  • System Preferences
  • App Notifications
  • A Better Menu Bar
  • Device Event Handling
  • Date picker
  • App Color picker
  • Dialog Boxes
  • Customizable Bottom Bar
  • Loading Screen
  • Time Zones

Installing

I don't like the version number suggested in the 'Installing' page. Instead, always go up to the 'major' semantic version number when installing this library package. This means always 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. Example, to install version 7.9.2, use 7.0.0. Thus, the bug fix, 7.9.2, will be installed the next time you 'upgrade' the dependencies.

  1. patch - bugfixes
  2. minor - Introduced new features
  3. major - 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:

dependencies:
mvc_application:^8.9.0

For more information on version numbers: The importance of semantic versioning.

Note, in fact, this package serves as a 'wrapper' for the core MVC package:

MVC Pattern

Pub.devGitHub starsLast Commit

Usage

Like many other design patterns, MVC separates three common 'areas of concern' found in computer programs. Specifically, there's the separation of the program's logic from its inteface and from its data. The many design patterns that have come into vogue since MVC's inception in the early 1970's are decendants of MVC.

Note, computer platforms themselves have come full circle in the last 40 years. From mainframes to desktop computers in the first twenty years, and then website applications to mobile apps in the last twenty years. A computer now fits in the palm of your hand, and because of this, MVC again fits as the design pattern and framework for the apps it runs. mvcTypes

Implementing the MVC framework using two common example apps:

The Counter App
import'package:mvc_application/view.dart';
import'package:mvc_application/controller.dart';
voidmain() =>runApp(MyApp());
classMyAppextendsAppStatefulWidget {
MyApp({Key? key}) :super(key: key);
@overrideAppStatecreateState() =>View();
}
classViewextendsAppState {
View()
:super(
title:'Flutter Demo',
home:constMyHomePage(),
debugShowCheckedModeBanner:false,
theme:ThemeData(
primarySwatch:Colors.blue,
),
);
}
classMyHomePageextendsStatefulWidget {
constMyHomePage({Key? key, this.title ='Flutter Demo Home Page'})
:super(key: key);
// Fields in a StatefulWidget should always be "final".finalString title;
@overrideStatecreateState() =>_MyHomePageState();
}
class_MyHomePageStateextendsStateMVC<MyHomePage> {
_MyHomePageState() :super(Controller()) {
con = controller asController;
}
lateController con;
@overrideWidgetbuild(BuildContext context) =>Scaffold(
appBar:AppBar(
title:Text(widget.title),
),
body:Center(
child:Column(
mainAxisAlignment:MainAxisAlignment.center,
children:<Widget>[
constText('You have pushed the button this many times:'),
Text(
'${con.counter}',
style:Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton:FloatingActionButton(
/// Try this alternative approach. /// The Controller merely mimics the Flutter's API// onPressed: con.onPressed,
onPressed: () =>setState(con.incrementCounter),
tooltip:'Increment',
child:constIcon(Icons.add),
),
);
}
classControllerextendsControllerMVC {
factoryController() => _this ??=Controller._();
Controller._()
: model =_Model(),
super();
staticController? _this;
final_Model model;
/// You're free to mimic Flutter's own API /// The Controller is able to talk to the View (the State object)voidonPressed() =>setState(() => model._incrementCounter());
intget counter => model.integer;
/// The Controller knows how to 'talk to' the Model.voidincrementCounter() => model._incrementCounter();
}
class_Model {
intget integer => _integer;
int _integer =0;
int_incrementCounter() =>++_integer;
}

Name Generator App

import'package:english_words/english_words.dart'show generateWordPairs;
import'package:flutter/material.dart'hide runApp;
import'package:mvc_application/view.dart'show AppMVC, AppState, Colors, runApp, StateMVC;
import'package:mvc_application/controller.dart'show ControllerMVC;
voidmain() =>runApp(NameApp());
classNameAppextendsAppMVC {
NameApp({Key? key}) :super(key: key);
@overrideAppStatecreateState() =>MyApp();
}
classMyAppextendsAppState {
factoryMyApp() => _this ??=MyApp._();
MyApp._()
:super(
title:'Startup Name Generator',
home:constRandomWords(),
theme:ThemeData(
primaryColor:Colors.white,
),
debugShowCheckedModeBanner:false,
);
staticMyApp? _this;
}
classRandomWordsextendsStatefulWidget {
constRandomWords({Key? key}) :super(key: key);
@overrideStatecreateState() =>_RandomWordsState();
}
class_RandomWordsStateextendsStateMVC<RandomWords> {
_RandomWordsState() :super(_Controller()) {
con = controller as_Controller;
}
late_Controller con;
finalTextStyle _biggerFont =constTextStyle(fontSize:18.0);
@overrideWidgetbuild(BuildContext context) {
returnScaffold(
appBar:AppBar(
title:constText('Startup Name Generator'),
actions:<Widget>[
IconButton(
icon:constIcon(Icons.list),
onPressed: () {
pushSaved(context);
}),
],
),
body:_buildSuggestions(),
);
}
Widget_buildSuggestions() {
returnListView.builder(
padding:constEdgeInsets.all(16.0),
itemBuilder: (context, i) {
// Add a one-pixel-high divider widget before each row in theListView.if (i.isOdd) returnconstDivider();
final index = i ~/2;
// If you've reached the end of the available word pairings...if (index >= con.length) {
// ...then generate 10 more and add them to the suggestions list.
con.addAll(10);
}
returnbuildRow(index);
},
);
}
voidpushSaved(BuildContext context) {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (BuildContext context) {
finalIterable<ListTile> tiles =this.tiles;
List<Widget> divided;
if (tiles.isEmpty) {
divided = [];
} else {
divided =ListTile.divideTiles(
context: context,
tiles: tiles,
).toList();
}
returnScaffold(
appBar:AppBar(
title:constText('Saved Suggestions'),
),
body:ListView(children: divided),
);
},
),
);
}
WidgetbuildRow(int? index) {
if (index ==null|| index <0) index =0;
String something = con.something(index);
final alreadySaved = con.contains(something);
returnListTile(
title:Text(
something,
style: _biggerFont,
),
trailing:Icon(
alreadySaved ?Icons.favorite :Icons.favorite_border,
color: alreadySaved ?Colors.red :null,
),
onTap: () {
setState(() {
con.somethingHappens(something);
});
},
);
}
Iterable<ListTile> get tiles => con.mapHappens(
(String something) {
returnListTile(
title:Text(
something,
style: _biggerFont,
),
);
},
);
}
class_ControllerextendsControllerMVC {
// Supply only one instance of this Controller class.factory_Controller() => _this ??=_Controller._();
static_Controller? _this;
_Controller._() {
model =_Model();
}
late_Model model;
intget length => model.length;
voidaddAll(int count) => model.addAll(count);
Stringsomething(int index) => model.wordPair(index);
boolcontains(String something) => model.contains(something);
voidsomethingHappens(String something) => model.save(something);
Iterable<ListTile> mapHappens<ListTile>(ListTileFunction(String v) f) =>
model.saved(f);
}
class_Model {
finalList<String> _suggestions = [];
intget length => _suggestions.length;
StringwordPair(int? index) {
if (index ==null|| index <0) index =0;
return _suggestions[index];
}
boolcontains(String? pair) {
if (pair ==null|| pair.isEmpty) returnfalse;
return _saved.contains(pair);
}
finalSet<String> _saved = {};
voidsave(String? pair) {
if (pair ==null|| pair.isEmpty) return;
final alreadySaved =contains(pair);
if (alreadySaved) {
_saved.remove(pair);
} else {
_saved.add(pair);
}
}
Iterable<ListTile> saved<ListTile>(ListTileFunction(String v) f) =>
_saved.map(f);
Iterable<String> wordPairs([int count =10]) =>_makeWordPairs(count);
voidaddAll(int count) => _suggestions.addAll(wordPairs(count));
}
Iterable<String> _makeWordPairs(int count) =>generateWordPairs().take(count).map((pair) => pair.asPascalCase);

Additional Documentation

Please begin with the article, ‘Flutter + MVC at Last!’online article

Follow up with Bazaar in MVC and Shrine in MVC and Weather App in "mvc pattern"bizzarMVCshrineMVCMVCWeatherApp

Optionally, there is the 3-part series beginning with, MVC in FlutterMVCFlutter

Further articles include, A Design Pattern for Flutter. designpattern

Other Dart Packages

packages Other Dart packages from the author can also be found at Pub.dev

About

Application Framework conveying the MVC design pattern.

Resources

Stars

27 stars

Watchers

1 watching

Forks

Releases

Packages

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 Application

Build StatusMediumPub.devGitHub starsLast Commit

A Flutter Framework using the MVC Design Pattern

mvc_application Allows for easier and, dare I say, faster development and better maintenability. No 're-inventing of the wheel' using already built-in capabilities and features offered by Flutter itself. Accommodating and Integrated features:

  • Error Handling
  • System Preferences
  • App Notifications
  • A Better Menu Bar
  • Device Event Handling
  • Date picker
  • App Color picker
  • Dialog Boxes
  • Customizable Bottom Bar
  • Loading Screen
  • Time Zones

Installing

I don't like the version number suggested in the 'Installing' page. Instead, always go up to the 'major' semantic version number when installing this library package. This means always 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. Example, to install version 7.9.2, use 7.0.0. Thus, the bug fix, 7.9.2, will be installed the next time you 'upgrade' the dependencies.

  1. patch - bugfixes
  2. minor - Introduced new features
  3. major - 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:

dependencies:
mvc_application:^8.9.0

For more information on version numbers: The importance of semantic versioning.

Note, in fact, this package serves as a 'wrapper' for the core MVC package:

MVC Pattern

Pub.devGitHub starsLast Commit

Usage

Like many other design patterns, MVC separates three common 'areas of concern' found in computer programs. Specifically, there's the separation of the program's logic from its inteface and from its data. The many design patterns that have come into vogue since MVC's inception in the early 1970's are decendants of MVC.

Note, computer platforms themselves have come full circle in the last 40 years. From mainframes to desktop computers in the first twenty years, and then website applications to mobile apps in the last twenty years. A computer now fits in the palm of your hand, and because of this, MVC again fits as the design pattern and framework for the apps it runs. mvcTypes

Implementing the MVC framework using two common example apps:

The Counter App
import'package:mvc_application/view.dart';
import'package:mvc_application/controller.dart';
voidmain() =>runApp(MyApp());
classMyAppextendsAppStatefulWidget {
MyApp({Key? key}) :super(key: key);
@overrideAppStatecreateState() =>View();
}
classViewextendsAppState {
View()
:super(
title:'Flutter Demo',
home:constMyHomePage(),
debugShowCheckedModeBanner:false,
theme:ThemeData(
primarySwatch:Colors.blue,
),
);
}
classMyHomePageextendsStatefulWidget {
constMyHomePage({Key? key, this.title ='Flutter Demo Home Page'})
:super(key: key);
// Fields in a StatefulWidget should always be "final".finalString title;
@overrideStatecreateState() =>_MyHomePageState();
}
class_MyHomePageStateextendsStateMVC<MyHomePage> {
_MyHomePageState() :super(Controller()) {
con = controller asController;
}
lateController con;
@overrideWidgetbuild(BuildContext context) =>Scaffold(
appBar:AppBar(
title:Text(widget.title),
),
body:Center(
child:Column(
mainAxisAlignment:MainAxisAlignment.center,
children:<Widget>[
constText('You have pushed the button this many times:'),
Text(
'${con.counter}',
style:Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton:FloatingActionButton(
/// Try this alternative approach. /// The Controller merely mimics the Flutter's API// onPressed: con.onPressed,
onPressed: () =>setState(con.incrementCounter),
tooltip:'Increment',
child:constIcon(Icons.add),
),
);
}
classControllerextendsControllerMVC {
factoryController() => _this ??=Controller._();
Controller._()
: model =_Model(),
super();
staticController? _this;
final_Model model;
/// You're free to mimic Flutter's own API /// The Controller is able to talk to the View (the State object)voidonPressed() =>setState(() => model._incrementCounter());
intget counter => model.integer;
/// The Controller knows how to 'talk to' the Model.voidincrementCounter() => model._incrementCounter();
}
class_Model {
intget integer => _integer;
int _integer =0;
int_incrementCounter() =>++_integer;
}

Name Generator App

import'package:english_words/english_words.dart'show generateWordPairs;
import'package:flutter/material.dart'hide runApp;
import'package:mvc_application/view.dart'show AppMVC, AppState, Colors, runApp, StateMVC;
import'package:mvc_application/controller.dart'show ControllerMVC;
voidmain() =>runApp(NameApp());
classNameAppextendsAppMVC {
NameApp({Key? key}) :super(key: key);
@overrideAppStatecreateState() =>MyApp();
}
classMyAppextendsAppState {
factoryMyApp() => _this ??=MyApp._();
MyApp._()
:super(
title:'Startup Name Generator',
home:constRandomWords(),
theme:ThemeData(
primaryColor:Colors.white,
),
debugShowCheckedModeBanner:false,
);
staticMyApp? _this;
}
classRandomWordsextendsStatefulWidget {
constRandomWords({Key? key}) :super(key: key);
@overrideStatecreateState() =>_RandomWordsState();
}
class_RandomWordsStateextendsStateMVC<RandomWords> {
_RandomWordsState() :super(_Controller()) {
con = controller as_Controller;
}
late_Controller con;
finalTextStyle _biggerFont =constTextStyle(fontSize:18.0);
@overrideWidgetbuild(BuildContext context) {
returnScaffold(
appBar:AppBar(
title:constText('Startup Name Generator'),
actions:<Widget>[
IconButton(
icon:constIcon(Icons.list),
onPressed: () {
pushSaved(context);
}),
],
),
body:_buildSuggestions(),
);
}
Widget_buildSuggestions() {
returnListView.builder(
padding:constEdgeInsets.all(16.0),
itemBuilder: (context, i) {
// Add a one-pixel-high divider widget before each row in theListView.if (i.isOdd) returnconstDivider();
final index = i ~/2;
// If you've reached the end of the available word pairings...if (index >= con.length) {
// ...then generate 10 more and add them to the suggestions list.
con.addAll(10);
}
returnbuildRow(index);
},
);
}
voidpushSaved(BuildContext context) {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (BuildContext context) {
finalIterable<ListTile> tiles =this.tiles;
List<Widget> divided;
if (tiles.isEmpty) {
divided = [];
} else {
divided =ListTile.divideTiles(
context: context,
tiles: tiles,
).toList();
}
returnScaffold(
appBar:AppBar(
title:constText('Saved Suggestions'),
),
body:ListView(children: divided),
);
},
),
);
}
WidgetbuildRow(int? index) {
if (index ==null|| index <0) index =0;
String something = con.something(index);
final alreadySaved = con.contains(something);
returnListTile(
title:Text(
something,
style: _biggerFont,
),
trailing:Icon(
alreadySaved ?Icons.favorite :Icons.favorite_border,
color: alreadySaved ?Colors.red :null,
),
onTap: () {
setState(() {
con.somethingHappens(something);
});
},
);
}
Iterable<ListTile> get tiles => con.mapHappens(
(String something) {
returnListTile(
title:Text(
something,
style: _biggerFont,
),
);
},
);
}
class_ControllerextendsControllerMVC {
// Supply only one instance of this Controller class.factory_Controller() => _this ??=_Controller._();
static_Controller? _this;
_Controller._() {
model =_Model();
}
late_Model model;
intget length => model.length;
voidaddAll(int count) => model.addAll(count);
Stringsomething(int index) => model.wordPair(index);
boolcontains(String something) => model.contains(something);
voidsomethingHappens(String something) => model.save(something);
Iterable<ListTile> mapHappens<ListTile>(ListTileFunction(String v) f) =>
model.saved(f);
}
class_Model {
finalList<String> _suggestions = [];
intget length => _suggestions.length;
StringwordPair(int? index) {
if (index ==null|| index <0) index =0;
return _suggestions[index];
}
boolcontains(String? pair) {
if (pair ==null|| pair.isEmpty) returnfalse;
return _saved.contains(pair);
}
finalSet<String> _saved = {};
voidsave(String? pair) {
if (pair ==null|| pair.isEmpty) return;
final alreadySaved =contains(pair);
if (alreadySaved) {
_saved.remove(pair);
} else {
_saved.add(pair);
}
}
Iterable<ListTile> saved<ListTile>(ListTileFunction(String v) f) =>
_saved.map(f);
Iterable<String> wordPairs([int count =10]) =>_makeWordPairs(count);
voidaddAll(int count) => _suggestions.addAll(wordPairs(count));
}
Iterable<String> _makeWordPairs(int count) =>generateWordPairs().take(count).map((pair) => pair.asPascalCase);

Additional Documentation

Please begin with the article, ‘Flutter + MVC at Last!’online article

Follow up with Bazaar in MVC and Shrine in MVC and Weather App in "mvc pattern"bizzarMVCshrineMVCMVCWeatherApp

Optionally, there is the 3-part series beginning with, MVC in FlutterMVCFlutter

Further articles include, A Design Pattern for Flutter. designpattern

Other Dart Packages

packages Other Dart packages from the author can also be found at Pub.dev

About

Application Framework conveying the MVC design pattern.

Resources

Stars

27 stars

Watchers

1 watching

Forks

Releases

Packages

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 Application

Build StatusMediumPub.devGitHub starsLast Commit

A Flutter Framework using the MVC Design Pattern

mvc_application Allows for easier and, dare I say, faster development and better maintenability. No 're-inventing of the wheel' using already built-in capabilities and features offered by Flutter itself. Accommodating and Integrated features:

  • Error Handling
  • System Preferences
  • App Notifications
  • A Better Menu Bar
  • Device Event Handling
  • Date picker
  • App Color picker
  • Dialog Boxes
  • Customizable Bottom Bar
  • Loading Screen
  • Time Zones

Installing

I don't like the version number suggested in the 'Installing' page. Instead, always go up to the 'major' semantic version number when installing this library package. This means always 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. Example, to install version 7.9.2, use 7.0.0. Thus, the bug fix, 7.9.2, will be installed the next time you 'upgrade' the dependencies.

  1. patch - bugfixes
  2. minor - Introduced new features
  3. major - 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:

dependencies:
mvc_application:^8.9.0

For more information on version numbers: The importance of semantic versioning.

Note, in fact, this package serves as a 'wrapper' for the core MVC package:

MVC Pattern

Pub.devGitHub starsLast Commit

Usage

Like many other design patterns, MVC separates three common 'areas of concern' found in computer programs. Specifically, there's the separation of the program's logic from its inteface and from its data. The many design patterns that have come into vogue since MVC's inception in the early 1970's are decendants of MVC.

Note, computer platforms themselves have come full circle in the last 40 years. From mainframes to desktop computers in the first twenty years, and then website applications to mobile apps in the last twenty years. A computer now fits in the palm of your hand, and because of this, MVC again fits as the design pattern and framework for the apps it runs. mvcTypes

Implementing the MVC framework using two common example apps:

The Counter App
import'package:mvc_application/view.dart';
import'package:mvc_application/controller.dart';
voidmain() =>runApp(MyApp());
classMyAppextendsAppStatefulWidget {
MyApp({Key? key}) :super(key: key);
@overrideAppStatecreateState() =>View();
}
classViewextendsAppState {
View()
:super(
title:'Flutter Demo',
home:constMyHomePage(),
debugShowCheckedModeBanner:false,
theme:ThemeData(
primarySwatch:Colors.blue,
),
);
}
classMyHomePageextendsStatefulWidget {
constMyHomePage({Key? key, this.title ='Flutter Demo Home Page'})
:super(key: key);
// Fields in a StatefulWidget should always be "final".finalString title;
@overrideStatecreateState() =>_MyHomePageState();
}
class_MyHomePageStateextendsStateMVC<MyHomePage> {
_MyHomePageState() :super(Controller()) {
con = controller asController;
}
lateController con;
@overrideWidgetbuild(BuildContext context) =>Scaffold(
appBar:AppBar(
title:Text(widget.title),
),
body:Center(
child:Column(
mainAxisAlignment:MainAxisAlignment.center,
children:<Widget>[
constText('You have pushed the button this many times:'),
Text(
'${con.counter}',
style:Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton:FloatingActionButton(
/// Try this alternative approach. /// The Controller merely mimics the Flutter's API// onPressed: con.onPressed,
onPressed: () =>setState(con.incrementCounter),
tooltip:'Increment',
child:constIcon(Icons.add),
),
);
}
classControllerextendsControllerMVC {
factoryController() => _this ??=Controller._();
Controller._()
: model =_Model(),
super();
staticController? _this;
final_Model model;
/// You're free to mimic Flutter's own API /// The Controller is able to talk to the View (the State object)voidonPressed() =>setState(() => model._incrementCounter());
intget counter => model.integer;
/// The Controller knows how to 'talk to' the Model.voidincrementCounter() => model._incrementCounter();
}
class_Model {
intget integer => _integer;
int _integer =0;
int_incrementCounter() =>++_integer;
}

Name Generator App

import'package:english_words/english_words.dart'show generateWordPairs;
import'package:flutter/material.dart'hide runApp;
import'package:mvc_application/view.dart'show AppMVC, AppState, Colors, runApp, StateMVC;
import'package:mvc_application/controller.dart'show ControllerMVC;
voidmain() =>runApp(NameApp());
classNameAppextendsAppMVC {
NameApp({Key? key}) :super(key: key);
@overrideAppStatecreateState() =>MyApp();
}
classMyAppextendsAppState {
factoryMyApp() => _this ??=MyApp._();
MyApp._()
:super(
title:'Startup Name Generator',
home:constRandomWords(),
theme:ThemeData(
primaryColor:Colors.white,
),
debugShowCheckedModeBanner:false,
);
staticMyApp? _this;
}
classRandomWordsextendsStatefulWidget {
constRandomWords({Key? key}) :super(key: key);
@overrideStatecreateState() =>_RandomWordsState();
}
class_RandomWordsStateextendsStateMVC<RandomWords> {
_RandomWordsState() :super(_Controller()) {
con = controller as_Controller;
}
late_Controller con;
finalTextStyle _biggerFont =constTextStyle(fontSize:18.0);
@overrideWidgetbuild(BuildContext context) {
returnScaffold(
appBar:AppBar(
title:constText('Startup Name Generator'),
actions:<Widget>[
IconButton(
icon:constIcon(Icons.list),
onPressed: () {
pushSaved(context);
}),
],
),
body:_buildSuggestions(),
);
}
Widget_buildSuggestions() {
returnListView.builder(
padding:constEdgeInsets.all(16.0),
itemBuilder: (context, i) {
// Add a one-pixel-high divider widget before each row in theListView.if (i.isOdd) returnconstDivider();
final index = i ~/2;
// If you've reached the end of the available word pairings...if (index >= con.length) {
// ...then generate 10 more and add them to the suggestions list.
con.addAll(10);
}
returnbuildRow(index);
},
);
}
voidpushSaved(BuildContext context) {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (BuildContext context) {
finalIterable<ListTile> tiles =this.tiles;
List<Widget> divided;
if (tiles.isEmpty) {
divided = [];
} else {
divided =ListTile.divideTiles(
context: context,
tiles: tiles,
).toList();
}
returnScaffold(
appBar:AppBar(
title:constText('Saved Suggestions'),
),
body:ListView(children: divided),
);
},
),
);
}
WidgetbuildRow(int? index) {
if (index ==null|| index <0) index =0;
String something = con.something(index);
final alreadySaved = con.contains(something);
returnListTile(
title:Text(
something,
style: _biggerFont,
),
trailing:Icon(
alreadySaved ?Icons.favorite :Icons.favorite_border,
color: alreadySaved ?Colors.red :null,
),
onTap: () {
setState(() {
con.somethingHappens(something);
});
},
);
}
Iterable<ListTile> get tiles => con.mapHappens(
(String something) {
returnListTile(
title:Text(
something,
style: _biggerFont,
),
);
},
);
}
class_ControllerextendsControllerMVC {
// Supply only one instance of this Controller class.factory_Controller() => _this ??=_Controller._();
static_Controller? _this;
_Controller._() {
model =_Model();
}
late_Model model;
intget length => model.length;
voidaddAll(int count) => model.addAll(count);
Stringsomething(int index) => model.wordPair(index);
boolcontains(String something) => model.contains(something);
voidsomethingHappens(String something) => model.save(something);
Iterable<ListTile> mapHappens<ListTile>(ListTileFunction(String v) f) =>
model.saved(f);
}
class_Model {
finalList<String> _suggestions = [];
intget length => _suggestions.length;
StringwordPair(int? index) {
if (index ==null|| index <0) index =0;
return _suggestions[index];
}
boolcontains(String? pair) {
if (pair ==null|| pair.isEmpty) returnfalse;
return _saved.contains(pair);
}
finalSet<String> _saved = {};
voidsave(String? pair) {
if (pair ==null|| pair.isEmpty) return;
final alreadySaved =contains(pair);
if (alreadySaved) {
_saved.remove(pair);
} else {
_saved.add(pair);
}
}
Iterable<ListTile> saved<ListTile>(ListTileFunction(String v) f) =>
_saved.map(f);
Iterable<String> wordPairs([int count =10]) =>_makeWordPairs(count);
voidaddAll(int count) => _suggestions.addAll(wordPairs(count));
}
Iterable<String> _makeWordPairs(int count) =>generateWordPairs().take(count).map((pair) => pair.asPascalCase);

Additional Documentation

Please begin with the article, ‘Flutter + MVC at Last!’online article

Follow up with Bazaar in MVC and Shrine in MVC and Weather App in "mvc pattern"bizzarMVCshrineMVCMVCWeatherApp

Optionally, there is the 3-part series beginning with, MVC in FlutterMVCFlutter

Further articles include, A Design Pattern for Flutter. designpattern

Other Dart Packages

packages Other Dart packages from the author can also be found at Pub.dev

About

Application Framework conveying the MVC design pattern.

Resources

Stars

27 stars

Watchers

1 watching

Forks

Releases

Packages

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 Application

Build StatusMediumPub.devGitHub starsLast Commit

A Flutter Framework using the MVC Design Pattern

mvc_application Allows for easier and, dare I say, faster development and better maintenability. No 're-inventing of the wheel' using already built-in capabilities and features offered by Flutter itself. Accommodating and Integrated features:

  • Error Handling
  • System Preferences
  • App Notifications
  • A Better Menu Bar
  • Device Event Handling
  • Date picker
  • App Color picker
  • Dialog Boxes
  • Customizable Bottom Bar
  • Loading Screen
  • Time Zones

Installing

I don't like the version number suggested in the 'Installing' page. Instead, always go up to the 'major' semantic version number when installing this library package. This means always 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. Example, to install version 7.9.2, use 7.0.0. Thus, the bug fix, 7.9.2, will be installed the next time you 'upgrade' the dependencies.

  1. patch - bugfixes
  2. minor - Introduced new features
  3. major - 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:

dependencies:
mvc_application:^8.9.0

For more information on version numbers: The importance of semantic versioning.

Note, in fact, this package serves as a 'wrapper' for the core MVC package:

MVC Pattern

Pub.devGitHub starsLast Commit

Usage

Like many other design patterns, MVC separates three common 'areas of concern' found in computer programs. Specifically, there's the separation of the program's logic from its inteface and from its data. The many design patterns that have come into vogue since MVC's inception in the early 1970's are decendants of MVC.

Note, computer platforms themselves have come full circle in the last 40 years. From mainframes to desktop computers in the first twenty years, and then website applications to mobile apps in the last twenty years. A computer now fits in the palm of your hand, and because of this, MVC again fits as the design pattern and framework for the apps it runs. mvcTypes

Implementing the MVC framework using two common example apps:

The Counter App
import'package:mvc_application/view.dart';
import'package:mvc_application/controller.dart';
voidmain() =>runApp(MyApp());
classMyAppextendsAppStatefulWidget {
MyApp({Key? key}) :super(key: key);
@overrideAppStatecreateState() =>View();
}
classViewextendsAppState {
View()
:super(
title:'Flutter Demo',
home:constMyHomePage(),
debugShowCheckedModeBanner:false,
theme:ThemeData(
primarySwatch:Colors.blue,
),
);
}
classMyHomePageextendsStatefulWidget {
constMyHomePage({Key? key, this.title ='Flutter Demo Home Page'})
:super(key: key);
// Fields in a StatefulWidget should always be "final".finalString title;
@overrideStatecreateState() =>_MyHomePageState();
}
class_MyHomePageStateextendsStateMVC<MyHomePage> {
_MyHomePageState() :super(Controller()) {
con = controller asController;
}
lateController con;
@overrideWidgetbuild(BuildContext context) =>Scaffold(
appBar:AppBar(
title:Text(widget.title),
),
body:Center(
child:Column(
mainAxisAlignment:MainAxisAlignment.center,
children:<Widget>[
constText('You have pushed the button this many times:'),
Text(
'${con.counter}',
style:Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton:FloatingActionButton(
/// Try this alternative approach. /// The Controller merely mimics the Flutter's API// onPressed: con.onPressed,
onPressed: () =>setState(con.incrementCounter),
tooltip:'Increment',
child:constIcon(Icons.add),
),
);
}
classControllerextendsControllerMVC {
factoryController() => _this ??=Controller._();
Controller._()
: model =_Model(),
super();
staticController? _this;
final_Model model;
/// You're free to mimic Flutter's own API /// The Controller is able to talk to the View (the State object)voidonPressed() =>setState(() => model._incrementCounter());
intget counter => model.integer;
/// The Controller knows how to 'talk to' the Model.voidincrementCounter() => model._incrementCounter();
}
class_Model {
intget integer => _integer;
int _integer =0;
int_incrementCounter() =>++_integer;
}

Name Generator App

import'package:english_words/english_words.dart'show generateWordPairs;
import'package:flutter/material.dart'hide runApp;
import'package:mvc_application/view.dart'show AppMVC, AppState, Colors, runApp, StateMVC;
import'package:mvc_application/controller.dart'show ControllerMVC;
voidmain() =>runApp(NameApp());
classNameAppextendsAppMVC {
NameApp({Key? key}) :super(key: key);
@overrideAppStatecreateState() =>MyApp();
}
classMyAppextendsAppState {
factoryMyApp() => _this ??=MyApp._();
MyApp._()
:super(
title:'Startup Name Generator',
home:constRandomWords(),
theme:ThemeData(
primaryColor:Colors.white,
),
debugShowCheckedModeBanner:false,
);
staticMyApp? _this;
}
classRandomWordsextendsStatefulWidget {
constRandomWords({Key? key}) :super(key: key);
@overrideStatecreateState() =>_RandomWordsState();
}
class_RandomWordsStateextendsStateMVC<RandomWords> {
_RandomWordsState() :super(_Controller()) {
con = controller as_Controller;
}
late_Controller con;
finalTextStyle _biggerFont =constTextStyle(fontSize:18.0);
@overrideWidgetbuild(BuildContext context) {
returnScaffold(
appBar:AppBar(
title:constText('Startup Name Generator'),
actions:<Widget>[
IconButton(
icon:constIcon(Icons.list),
onPressed: () {
pushSaved(context);
}),
],
),
body:_buildSuggestions(),
);
}
Widget_buildSuggestions() {
returnListView.builder(
padding:constEdgeInsets.all(16.0),
itemBuilder: (context, i) {
// Add a one-pixel-high divider widget before each row in theListView.if (i.isOdd) returnconstDivider();
final index = i ~/2;
// If you've reached the end of the available word pairings...if (index >= con.length) {
// ...then generate 10 more and add them to the suggestions list.
con.addAll(10);
}
returnbuildRow(index);
},
);
}
voidpushSaved(BuildContext context) {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (BuildContext context) {
finalIterable<ListTile> tiles =this.tiles;
List<Widget> divided;
if (tiles.isEmpty) {
divided = [];
} else {
divided =ListTile.divideTiles(
context: context,
tiles: tiles,
).toList();
}
returnScaffold(
appBar:AppBar(
title:constText('Saved Suggestions'),
),
body:ListView(children: divided),
);
},
),
);
}
WidgetbuildRow(int? index) {
if (index ==null|| index <0) index =0;
String something = con.something(index);
final alreadySaved = con.contains(something);
returnListTile(
title:Text(
something,
style: _biggerFont,
),
trailing:Icon(
alreadySaved ?Icons.favorite :Icons.favorite_border,
color: alreadySaved ?Colors.red :null,
),
onTap: () {
setState(() {
con.somethingHappens(something);
});
},
);
}
Iterable<ListTile> get tiles => con.mapHappens(
(String something) {
returnListTile(
title:Text(
something,
style: _biggerFont,
),
);
},
);
}
class_ControllerextendsControllerMVC {
// Supply only one instance of this Controller class.factory_Controller() => _this ??=_Controller._();
static_Controller? _this;
_Controller._() {
model =_Model();
}
late_Model model;
intget length => model.length;
voidaddAll(int count) => model.addAll(count);
Stringsomething(int index) => model.wordPair(index);
boolcontains(String something) => model.contains(something);
voidsomethingHappens(String something) => model.save(something);
Iterable<ListTile> mapHappens<ListTile>(ListTileFunction(String v) f) =>
model.saved(f);
}
class_Model {
finalList<String> _suggestions = [];
intget length => _suggestions.length;
StringwordPair(int? index) {
if (index ==null|| index <0) index =0;
return _suggestions[index];
}
boolcontains(String? pair) {
if (pair ==null|| pair.isEmpty) returnfalse;
return _saved.contains(pair);
}
finalSet<String> _saved = {};
voidsave(String? pair) {
if (pair ==null|| pair.isEmpty) return;
final alreadySaved =contains(pair);
if (alreadySaved) {
_saved.remove(pair);
} else {
_saved.add(pair);
}
}
Iterable<ListTile> saved<ListTile>(ListTileFunction(String v) f) =>
_saved.map(f);
Iterable<String> wordPairs([int count =10]) =>_makeWordPairs(count);
voidaddAll(int count) => _suggestions.addAll(wordPairs(count));
}
Iterable<String> _makeWordPairs(int count) =>generateWordPairs().take(count).map((pair) => pair.asPascalCase);

Additional Documentation

Please begin with the article, ‘Flutter + MVC at Last!’online article

Follow up with Bazaar in MVC and Shrine in MVC and Weather App in "mvc pattern"bizzarMVCshrineMVCMVCWeatherApp

Optionally, there is the 3-part series beginning with, MVC in FlutterMVCFlutter

Further articles include, A Design Pattern for Flutter. designpattern

Other Dart Packages

packages Other Dart packages from the author can also be found at Pub.dev

About

Application Framework conveying the MVC design pattern.

Resources

Stars

27 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages