패키지, 저장소 및 앱 카테고리 검색

mvvm_plus 표지
패키지 상세v1.5.4

mvvm_plus

MVVM+는 MVVM의 가벼운 플러터 구현이며, 전역 레지스트리(GetIt 등) 또는 상속 위젯(Provider 등)을 통해 상태를 공유하는 로케이터도 포함하고 있습니다.

AndroidiOSWebmacOSWindowsLinux
30일 다운로드
301
좋아요
44
Pub 점수
150

패키지 설치

flutter pub add mvvm_plus

패키지 개요

공개 정보를 바탕으로 생성한 선택 가이드입니다.

MVVM+는 MVVM 아키텍처 패턴을 구현하는 가벼운 Flutter 패키지로, 상태 관리와 종속성 주입을 간소화합니다. GetIt과 유사한 전역 레지스트리를 제공하며, 효율적인 데이터 전파를 위해 Provider와 같은 상속 위젯을 지원합니다. 단순성과 유연성을 고려하여 설계되어 개발자가 명확한 관심사 분리를 통해 코드를 조직하고 반복 코드를 줄이는 데 도움이 됩니다. 여러 플랫폼에서 최소한의 효과적인 상태 처리 방법을 원하는 프로젝트에 적합합니다.

사용 사례

  • 확장 가능한 Flutter 애플리케이션 빌드
  • 공유 애플리케이션 상태 관리
  • 의존성 주입 간소화
  • 사용자 인터페이스와 비즈니스 로직의 명확한 분리 가능
  • 크로스 플랫폼 개발 지원

주요 기능

  • 경량 MVVM 구현
  • 전역 상태 레지스트리(예: GetIt)
  • 상속 위젯(예: Provider)에 대한 지원
  • 최소한의 보일러플레이트
  • 크로스 플랫폼 호환성

적합한 대상

  • MVVM에 익숙하지 않은 개발자
  • 간단한 상태 관리가 필요한 프로젝트
  • 경량 DI 솔루션을 원하는 팀
  • 다중 플랫폼을 대상으로 하는 Flutter 앱
  • 클린 아키텍처가 필요한 앱

고려 사항

  • 복잡한 상태 흐름을 처리하기 위해 추가 설정이 필요할 수 있습니다
  • 기능이 풀 기능 상태 관리 솔루션보다 적은
  • 중규모 애플리케이션에 가장 적합
  • 매우 크거나 매우 동적인 앱에는 적합하지 않음
  • 성숙한 대안에 비해 문서화가 제한적

원본 README

연결된 소스 저장소의 README 원문 스냅샷입니다. 최신 내용은 저장소에서 확인하세요.

mvvm_plus (mvvm+)

mvvm plus logo

MVVM+ is a lightweight Flutter implementation of MVVM, plus a locator for sharing states via a global registry (like GetIt) or inherited widgets (like Provider).

YouTube videos

A one-minute introduction to MVVM+:

short vid

A longer demo video:

long vid

Tiny API

MVVM+ extends existing Flutter classes and introduces three methods: get, listenTo, and buildView:

  • Model extends ChangeNotifier and adds:
    • get
    • listenTo
  • ViewWidget extends StatefulWidget/State and adds:
    • get
    • listenTo
  • ViewModel extends Model and adds:
    • buildView
  • Property is a typedef of ValueNotifier, so adds nothing.

But don't be fooled by MVVM+'s minimal interface. MVVM+ is a full implementation of MVVM.

Model-View-View Model (MVVM)

As with all MVVM implementations, MVVM+ organizes UI into an object called the View. Business logic associated with a View is organized into an object called a View Model. Business logic that spans two or more View Models is organized into one or more Models.

mvvm flow

States are mutated in the View Model and the Model, but not the View. With MVVM+, the View is a Flutter widget and the View Model and Model are Dart models that extend ChangeNotifier.

MVVM+ goals:

  • Clearly separate business logic and state from UI.
  • Support access to models in a global registry (like GetIt).
  • Support access to models from descendant widgets (like Provider, InheritedWidget).
  • Work well alone or with other state management packages (BLoC, RxDart, Provider, GetIt, ...).
  • Be scalable and performant, so suitable for both indy and production apps.
  • Be simple.
  • Be small.

Views and View Models

To create a View Model, extend ViewModel:

class MyWidgetViewModel extends ViewModel {
  String someText;
}

To create a View, extend ViewWidget. You give the super constructor a builder for your ViewModel (via the "builder" parameter) and you override ViewWidget's build function (just like StatelessWidget):

class MyWidget extends ViewWidget<MyWidgetViewModel> {
  MyWidget({super.key}) : super(builder: () => MyWidgetViewModel());

  @override
  Widget build(BuildContext context) {
    return Text(viewModel.someText); // <- your "viewModel" getter
  }
}

Views are frequently nested and can be large, like an app page or feature. Or small, like a password field or a button.

VSCode mvvm+ extension

The boilerplate for the ViewWidget and ViewModel classes is very similar to that of StatefulWidget and its State class. So, like the Flutter extension adds a "stful" snippet for writing StatefulWidget boilerplate, the mvvm plus extension adds a snippet for writing ViewWidget and ViewModel classes.

mvvm plus extension

Search the VSCode extension marketplace for "mvvm plus". After installing the extension, just start typing "mvvm+" in the edit window and hit Enter when the mvvm+ snippet is highlighted. Then type the name of your widget and the extension will populate the naming for you. Hit tab to edit the build function.

Rebuilding a ViewWidget

ViewModel includes a buildView method for rebuilding the ViewWidget. You can call it explicitly:

class MyWidgetViewModel extends ViewModel {
  int counter;

  void incrementCounter() {
    counter++;
    buildView(); // <- queues ViewWidget to build
  }
}

Or use buildView as a listener to bind the ViewModel to the ViewWidget with a ValueNotifier:

late final counter = ValueNotifier<int>(0)..addListener(buildView);

Because typing ..addListener(buildView) for every property can get tedious, ViewModel has a convenience method createProperty that adds the buildView listener for you. So you could refactor the line above as:

late final counter = createProperty<int>(0);

initState and dispose

Like the Flutter State class associated with StatefulWidget, the ViewModel class has initState and dispose member functions which are handy for initialization and teardown.

class MyWidgetViewModel extends ViewModel {
  late final streamCounter = createStreamProperty<int>(Stream.value(0));

  @override
  void dispose() {
    streamCounter.dispose();
    super.dispose();
  }
}

Adding and getting ViewModels from the registry

Occasionally you need to access another widget's ViewModel instance (e.g., if it's an ancestor or on another branch of the widget tree). To make a ViewModel globally available, use the ViewWidget specifier location: Location.registry:

class MyOtherWidget extends ViewWidget<MyOtherWidgetViewModel> {
  MyOtherWidget(super.key) : super(
    location: Location.registry, // <- Adds the ViewModel to the registry
    builder: () => MyOtherWidgetViewModel());
}

Views and ViewModels anywhere on the widget tree can access the ViewModel with their get or listenTo methods.


final otherViewModel = get<MyOtherWidgetViewModel>();
final otherViewModel = listenTo<MyOtherWidgetViewModel>();

Like GetIt, registered ViewModels are not managed by InheritedWidget. So, widgets don't need to be children of a ViewWidget to get its registered ViewModel. This is a big plus for use cases where the accessed ViewModel is not an ancestor.

Unlike GetIt the lifecycle of all ViewModel instances (including registered) are bound to the lifecycle of the ViewWidget instances that instantiated them. So, when a ViewWidget instance is removed from the widget tree, its ViewModel is disposed.

On rare occasions when you need to register multiple ViewModels of the same type, just give each ViewModel instance a unique name:

class MyOtherWidget extends ViewWidget<MyOtherWidgetViewModel> {
  MyOtherWidget(super.key) : super(
    location: Location.registry,
    name: 'Header', // <- unique name
    builder: () => MyOtherWidgetViewModel());
}

and then get the ViewModel by type and name:


final headerText = get<MyOtherWidgetViewModel>(name: 'Header').someText;
final footerText = get<MyOtherWidgetViewModel>(name: 'Footer').someText;

Alternatively, make ViewModels inherited (like Provider, InheritedWidget)

Instead of using the global registry, you have the option of adding ViewModels to the widget tree. Just add the specifier location: Location.tree, which makes the ViewModel available to descendants:

class MyOtherWidget extends ViewWidget<MyOtherWidgetViewModel> {
  MyOtherWidget(super.key) : super(
    location: Location.tree, // <- Puts ViewModel on the widget tree
    builder: () => MyOtherWidgetViewModel());
}

Views and ViewModels that are descendants can use their context and get or listenTo functions to access the ViewModel.


final otherViewModel = get<MyOtherWidgetViewModel>(context: context);
final otherViewModel = listenTo<MyOtherWidgetViewModel>(context: context);

Models

The Model class is a super class of ViewModel with much of the functionality of ViewModel. MVVM+ uses the Bilocator package under the hood which has a widget named "Bilocator" that adds Models to the widget tree:

Bilocator<MyModel>(
  builder: () => MyModel(),
  child: MyWidget(),
);

By default, the Bilocator widget adds its model to the global registry when added to the widget tree and unregisters it when removed. (To register multiple models with a single widget, check out Bilocators).

As with the ViewWidget class, to add a model to the widget tree (instead of the registry), simply specify the location to Location.tree:

Bilocator<MyModel>(
  builder: () => MyModel(),
  location: Location.tree, // <- Adds model to widget tree instead of global registry
  child: MyWidget(),
);

Listening to other widget's ViewModels

The get method of ViewWidget and ViewModel retrieves registered ViewModels but does not listen for future changes. For that, use listenTo from within your ViewModel:

final someText = listenTo<MyOtherWidgetViewModel>().someText;

listenTo performs a one-time add of the buildView method as a listener that is called every time the notifyListeners method of MyOtherWidgetViewModel is called. If you want to do more than just queue a build, you can give listenTo a custom listener function:

final someText = listenTo<MyWidgetViewModel>(listener: myListener).someText;

If you want to rebuild your ViewWidget after your custom listener finishes, just call buildView within your listener:

void myListener() {
  // do some stuff
  buildView();
}

Either way, listeners added by listenTo are automatically removed when your ViewModel instance is disposed.

notifyListeners vs buildView

When your ViewWidget and ViewModel classes are instantiated, buildView is added as a listener to your ViewModel. So, calling buildView or notifyListeners from within your ViewModel will both rebuild your ViewWidget. So, what's the difference between calling buildView and notifyListeners? Nothing, unless your ViewModel has other listeners. So, to eliminate unnecessary ViewWidget builds, it is a best practice to use buildView unless your use case requires listeners to be notified of a change.

ValueNotifiers are your MVVM Properties!

The MVVM pattern uses the term "Properties" to describe public values of View Models that are bound to Views and other objects. I.e., when the Property is changed, listeners are notified:

class MyViewModel {
  final counter = Property<int>(0);
}

In Flutter, this is how ValueNotifiers work. So, MVVM+ adds a typedef that equates Property with ValueNotifier. As you use MVVM+, feel free to call your public members of ViewModels "Properties" or "ValueNotifiers", whichever is more comfortable to you. (In the MVVM+ documentation, I use " ValueNotifier" to be more transparent with the Flutter underpinnings, but in practice, I prefer to use "Property" because it clarifies its purpose and because "Property" has fewer characters! :)

So, for more granularity than listening to an entire registered Model, you can get a model and listenTo one of its ValueNotifiers/Properties:

final cloud = get<CloudService>();
final currentUser = listenTo<ValueNotifier<User>>(notifier: cloud.currentUser).value;

FutureProperty and StreamProperty

MVVM+ supports Flutter Future and Stream with the FutureProperty and StreamProperty. When either of these resolves, its hasData field is set to true and its listeners are notified, enabling rendering to update and access the values through its data field.

@override
Widget build(BuildContext context) {
  return futureName.hasData
    ? Text('${futureName.data}')
    : CircularProgressIndicator();
}

For Streams, use the subscription getter for pausing or canceling listening to the stream.

late final streamCounter = createStreamProperty<int>(Stream.value(0));
        :
streamCounter.subscription.pause();
        :
streamCounter.subscription.cancel();

Alternatively, the StreamProperty.dispose() cancels the subscription.

Mixins

The ViewWidget class is an extensions of a StatefulWidget, complete with the usual State class under the hood (named ViewState) that manages View and its ViewModel. Some packages include a mixin that must be mixed in with State. For this, you can extend ViewState to include your mixin. See the example below:

class MyWidget extends ViewWidget<MyWidgetViewModel> {
  MyWidget({super.key}) : super(builder: () => MyWidgetViewModel());

  @override
  // Overriding createState is only required when adding mixins
  MyWidgetState createState() => MyWidgetState();

  @override
  Widget build(BuildContext context) {
    // Use `getState` to retrieve your custom ViewState/mixin object
    return getState<MyWidgetState>().buildGreeting(viewModel.message.value);
  }
}

// Extend `ViewState` and add your mixin
class MyWidgetState extends ViewState<MyWidgetViewModel> with MyMixin {}

mixin MyMixin {
  buildGreeting(String message) => Text(message);
}

class MyWidgetViewModel extends ViewModel {
  late final message = createProperty<String>('Hello');
}

Additional documentation

For an in-the-weeds discussion of the code behind MVVM+, see my medium article How to Extend StatefulWidget into an MVVM Workhorse .

For an slightly higher level introduction to MVVM+, see my article Flutter State Management with MVVM+ . Please note that most of this ReadMe page overlaps with this article.

To learn more about the bilocator package that MVVM+ uses for its locator, see the bilocator documentation.

If you are migrating from Provider, see How to Migrate Your Flutter App from Provider to MVVM+.

Example

The source code for the repo example is under the Pub.dev "Example" tab and in the GitHub example/lib/main.dart file.

The example is a great way to familiarize yourself with MVVM+. It has several counters which demonstrate most of the classes in MVVM+.

example

That's it!

The example app demos much of the above functionality and shows how small and organized MVVM+ classes typically are.

If you have questions or suggestions on anything MVVM+, please do not hesitate to contact me.

카테고리

의존성

의존성 목록 1 개

  • bilocator^0.6.0

소스 저장소

이 패키지를 사용하는 프로젝트

아직 데이터가 없습니다.