Flutter中的MVVM设计模式

envsm3lx  于 2023-05-19  发布在  Flutter
关注(0)|答案(5)|浏览(125)

我们尝试开发一个Flutter应用程序,并创建一个有状态的小部件作为页面。
我们希望将构建函数与其他状态变量和状态函数分离在2个不同的文件中,构建函数可以访问我们创建类的状态类this

PageClassState extend State<PageClass>{
    string value = 'string value';
}

并在一个新的类中扩展它,这个类可以访问PageClassStatethis变量,我们写:

PageClassView extend PageClassState{
    @override
    Widget Build(){
      return(new Text(this.value))
    }
}

但是在PageClassState中,我们得到一个错误,说我们必须覆盖类中的build方法。有没有什么建议来解决这个问题,并在flutter中实现MVVM设计模式?

xpszyzbs

xpszyzbs1#

我建议将ViewModel代码移到一个不扩展State的单独类中。保持ViewModel平台独立。您的Widget状态可以具有viewModel的示例并与之交互。
您可以找到more detailed example here
如果子Widget需要访问您的ViewModel,您可以使用@Rémi Rousselet建议的继承Widget。我很快就为你实现了这一点:

class ViewModelProvider extends InheritedWidget {
  final ViewModel viewModel;

  ViewModelProvider({Key key, @required this.viewModel, Widget child}) 
  : super(key: key, child: child);

  @override
  bool updateShouldNotify(InheritedWidget oldWidget) => true;

  static ViewModel of(BuildContext context) =>
      (context.inheritFromWidgetOfExactType(ViewModelProvider) as 
  ViewModelProvider).viewModel;
}

子部件可以通过调用

var viewModel = ViewModelProvider.of(context);

让我知道如果你有任何问题:)

qij5mzcb

qij5mzcb2#

这不是正确的方法。你不应该拆分State<T>,它是build方法。问题是,不要扩展小部件。组成他们。
实现类似功能的正确方法是使用InheritedWidget。这些将保存您的数据,但不做其他任何事情。它的子节点将能够使用MyInherited.of(context)请求这些数据。
你也可以创建一个builder。类似于:

typedef Widget MyStateBuilder(BuildContext context, MyStateState state);

class MyState extends StatefulWidget {
  final MyStateState builder;

  const MyState({this.builder}) : assert(builder != null);

  @override
  MyStateState createState() => new MyStateState();
}

class MyStateState extends State<MyState> {
  String name;

  @override
  Widget build(BuildContext context) {
    return widget.builder(context, this);
  }
}
jogvjijk

jogvjijk3#

我一直在使用这个插件维护大型应用程序的Flutter。mvvm_flutter

https://pub.dev/packages/mvvm_flutter

它非常轻,易于使用检查一些例子。它很容易维护用户界面远离业务逻辑的

u59ebvdq

u59ebvdq4#

mvvm包,一个Flutter MVVM(Model-View-ViewModel)实现。

import 'package:flutter/widgets.dart';
import 'package:mvvm/mvvm.dart';
import 'dart:async';

// ViewModel
class Demo1ViewModel extends ViewModel {

  Demo1ViewModel() {
      // define bindable property
      propertyValue<String>(#time, initial: "");
      // timer
      start();
  }

  start() {
      Timer.periodic(const Duration(seconds: 1), (_) {
        var now = DateTime.now();
        // call setValue
        setValue<String>(#time, "${now.hour}:${now.minute}:${now.second}");
      });
  }
}

// View
class Demo1 extends View<Demo1ViewModel> {
  Demo1() : super(Demo1ViewModel());

  @override
  Widget buildCore(BuildContext context) {
    return Container(
        margin: EdgeInsets.symmetric(vertical: 100),
        padding: EdgeInsets.all(40),

        // binding
        child: $.watchFor(#time, 
            builder: $.builder1((t) => 
              Text(t, textDirection: TextDirection.ltr))));
  }
}

// run
void main() => runApp(Demo1());

full example

ix0qys7i

ix0qys7i5#

参考资料请看我的github https://github.com/anandh-ps/flutter_mvvm_example

MediaService.dart

import 'dart:convert';
import 'dart:io';
import 'package:meta/meta.dart';

import 'package:http/http.dart' as http;
import 'package:mvvm_flutter_app/model/apis/app_exception.dart';

class MediaService {
  final String _baseUrl = "https://itunes.apple.com/search?term=";

  Future<dynamic> get(String url) async {
    dynamic responseJson;
    try {
      final response = await http.get(_baseUrl + url);
      responseJson = returnResponse(response);
    } on SocketException {
      throw FetchDataException('No Internet Connection');
    }
    return responseJson;
  }

  @visibleForTesting
  dynamic returnResponse(http.Response response) {
    switch (response.statusCode) {
      case 200:
        dynamic responseJson = jsonDecode(response.body);
        return responseJson;
      case 400:
        throw BadRequestException(response.body.toString());
      case 401:
      case 403:
        throw UnauthorisedException(response.body.toString());
      case 500:
      default:
        throw FetchDataException(
            'Error occured while communication with server' +
                ' with status code : ${response.statusCode}');
    }
  }
}

MediaRepository.dart

import 'package:mvvm_flutter_app/model/media.dart';
import 'package:mvvm_flutter_app/model/services/media_service.dart';

class MediaRepository {
  MediaService _mediaService = MediaService();

  Future<List<Media>> fetchMediaList(String value) async {
    dynamic response = await _mediaService.get(value);
    final jsonData = response['results'] as List;
    List<Media> mediaList =
        jsonData.map((tagJson) => Media.fromJson(tagJson)).toList();
    return mediaList;
  }
}

MediaViewModel.dart

import 'package:flutter/cupertino.dart';
import 'package:mvvm_flutter_app/model/apis/api_response.dart';
import 'package:mvvm_flutter_app/model/media.dart';
import 'package:mvvm_flutter_app/model/media_repository.dart';

class MediaViewModel with ChangeNotifier {
  ApiResponse _apiResponse = ApiResponse.loading('Fetching artist data');

  Media _media;

  ApiResponse get response {
    return _apiResponse;
  }

  Media get media {
    return _media;
  }

  /// Call the media service and gets the data of requested media data of
  /// an artist.
  Future<void> fetchMediaData(String value) async {
    try {
      List<Media> mediaList = await MediaRepository().fetchMediaList(value);
      _apiResponse = ApiResponse.completed(mediaList);
    } catch (e) {
      _apiResponse = ApiResponse.error(e.toString());
      print(e);
    }
    notifyListeners();
  }

  void setSelectedMedia(Media media) {
    _media = media;
    notifyListeners();
  }
}

HomeScreen.dart

import 'package:flutter/material.dart';
import 'package:mvvm_flutter_app/model/apis/api_response.dart';
import 'package:mvvm_flutter_app/model/media.dart';
import 'package:mvvm_flutter_app/view/widgets/player_list_widget.dart';
import 'package:mvvm_flutter_app/view/widgets/player_widget.dart';
import 'package:mvvm_flutter_app/view_model/media_view_model.dart';

import 'package:provider/provider.dart';

class HomeScreen extends StatefulWidget {
  @override
  _HomeScreenState createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  @override
  void initState() {
    // TODO: implement initState
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    final _inputController = TextEditingController();
    ApiResponse apiResponse = Provider.of<MediaViewModel>(context).response;
    List<Media> mediaList = apiResponse.data as List<Media>;
    return Scaffold(
        appBar: AppBar(
          title: Text('Media Player'),
        ),
        body: Column(
          children: <Widget>[
            Padding(
              padding: const EdgeInsets.symmetric(vertical: 10.0),
              child: Row(
                children: <Widget>[
                  Expanded(
                    child: Container(
                      margin: EdgeInsets.symmetric(horizontal: 20.0),
                      decoration: BoxDecoration(
                        color: Theme.of(context).accentColor.withAlpha(50),
                        borderRadius: BorderRadius.circular(30.0),
                      ),
                      child: TextField(
                          style: TextStyle(
                            fontSize: 15.0,
                            color: Colors.grey,
                          ),
                          controller: _inputController,
                          onChanged: (value) {},
                          onSubmitted: (value) {
                            if (value.isNotEmpty) {
                              Provider.of<MediaViewModel>(context)
                                  .setSelectedMedia(null);
                              Provider.of<MediaViewModel>(context,
                                      listen: false)
                                  .fetchMediaData(value);
                            }
                          },
                          decoration: InputDecoration(
                            border: InputBorder.none,
                            enabledBorder: InputBorder.none,
                            focusedBorder: InputBorder.none,
                            prefixIcon: Icon(
                              Icons.search,
                              color: Colors.grey,
                            ),
                            hintText: 'Enter Artist Name',
                          )),
                    ),
                  ),
                ],
              ),
            ),
            mediaList != null && mediaList.length > 0
                ? Expanded(
                    child: PlayerListWidget(mediaList, (Media media) {
                    Provider.of<MediaViewModel>(context)
                        .setSelectedMedia(media);
                  }))
                : Expanded(
                    child: Center(
                      child: Text('Search the song by Artist'),
                    ),
                  ),
            if (Provider.of<MediaViewModel>(context).media != null)
              Align(
                  alignment: Alignment.bottomCenter,
                  child: PlayerWidget(
                    function: () {
                      setState(() {});
                    },
                  )),
          ],
        ));
  }
}

相关问题