jelly-2vm is a lightweight microframework for Flutter, based on the Model-View-ViewModel (MVVM) pattern, designed to enhance the maintainability, simplicity, and scalability of your applications. Born from AppFactory's experience with apps serving millions of users in Italy, jelly-2vm provides a clear and reactive structure for Flutter app development.
Read more about the philosophy and implementation of jelly-2vm in our article on Medium: jelly-2vm: A Lean MVVM Architecture for Scalable Flutter Applications
- Clear MVVM Architecture: Clean separation between View, ViewModel, and Service for more organized code.
- Optimized Reactivity: Targeted UI updates with
ChangeBuilder, avoiding unnecessary rebuilds. - Service Management: Services as singletons managed with
GetItfor easy dependency injection. - Reactive Services:
ServiceNotifierfor reactive communication between services. - Simplicity: Minimal API with only three main classes:
ViewModel,ViewWidget, andChangeBuilder. - Testability: Easy mocking and testing thanks to dependency injection.
- Scalability: Designed for large-scale applications and extended teams.
Add jelly-2vm to your pubspec.yaml dependencies:
dependencies:
jelly_2vm: ^latest_versionRun flutter pub get to install the package.
class CounterViewModel extends ViewModel {
CounterViewModel(this.count);
int count;
void increment(int amount) {
count += amount;
notifyListeners();
}
void decrement(int amount) {
count -= amount;
notifyListeners();
}
}class CounterView extends ViewWidget<CounterViewModel> {
CounterView({Key? key}) : super(key: key);
@override
CounterViewModel createViewModel() {
return CounterViewModel(0);
}
@override
Widget builder(BuildContext context, CounterViewModel counterViewModel) {
return Scaffold(
body: SafeArea(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
MaterialButton(
onPressed: () => counterViewModel.increment(1),
child: const Text("Increment"),
),
ChangeBuilder<CounterViewModel>(
listen: counterViewModel,
builder: (context, state) => Text("${state.count}"),
),
MaterialButton(
onPressed: () => counterViewModel.decrement(1),
child: const Text("Decrement"),
),
],
),
),
),
);
}
}Please refer to the Medium article for more details on the services and how to use them.
class StorageService {
StorageService();
Future<void> save(String key, String value) async {
final pref = await SharedPreferences.getInstance();
await pref.setString(key, value);
}
// ... other methods ...
}final getIt = GetIt.instance;
class Services {
static void init() {
getIt.registerSingleton<StorageService>(StorageService());
// ... other services ...
}
static T get<T extends Object>() {
return getIt.get<T>();
}
}mixin class ServiceNotifier {
final List<VoidCallback> _callbacks = [];
void addListener(VoidCallback callback) {
if (_callbacks.contains(callback)) {
return;
}
_callbacks.add(callback);
}
void removeListener(VoidCallback callback) {
_callbacks.remove(callback);
}
void notifyListeners() {
for (final c in _callbacks) {
try {
c();
} catch (e) {
AppLogger.d(e.toString());
}
}
}
}Inspired by Swift for iOS, it will help you to dispatch functions that will run on the View from your ViewModel.
Please refer to the docs for more details.
- A
View + ViewModelcan represent an entire page or a portion of it. - Views can use
StatelessWidgetfor layout sections. - Services are singletons managed with
GetIt. - Use
ServiceNotifierto make services reactive.
Quantums is the evolution of the Model-View-ViewModel pattern, in order to have a finer-grained reactivity and control over the state of the UI. Using quantums you can have a state of a specific part of the UI that can be observed and updated independently from the rest of the UI.
Sometimes having a state of the whole page can lead to performance issues, for this reason you can use a Quantum to represent the state of a specific part of the page. Using then the QuantumBuilder you can make the part reactive.
Let's take as an example a simple counter:
class CounterView extends ViewWidget<CounterViewModel> {
const CounterView({super.key});
@override
HomeViewModel createViewModel() {
return HomeViewModel(delegate: this);
}
@override
Widget build(BuildContext context, CounterViewModel viewModel) {
return Scaffold(
body: Center(
child: Column(
children: [
QuantumBuilder<int>(
quantum: viewModel.counter,
builder: (context, value) {
return Text('$value');
},
),
ElevatedButton(
onPressed: viewModel.increment,
child: const Text('Increment'),
),
],
),
),
);
}
}class CounterViewModel extends ViewModel {
final Quantum<int> counter = Quantum<int>(0);
void increment() {
counter.value++;
}
}For more examples, see the docs.
jelly-2vm is open-source! Feel free to contribute with pull requests, report bugs, or suggest new features.
jelly-2vm is released under the MIT License. See the LICENSE file for details.
For questions or support, please open an issue on GitHub.