rrousselGit / riverpod

A reactive caching and data-binding framework. Riverpod makes working with asynchronous code a breeze.
https://riverpod.dev
MIT License
5.96k stars 920 forks source link

Please Provides an architectural example of MVC + latest riverpod generator #3119

Open laterdayi opened 7 months ago

laterdayi commented 7 months ago

Is your feature request related to a problem? Please describe. As a powerful riverpod, I hope the official can provide MVC + the latest riverpod generator architecture example

Describe the solution you'd like As a powerful riverpod, I hope the official can provide MVC + the latest riverpod generator architecture example

laterdayi commented 7 months ago

For example: I have A page, A controller, in A controller store a lot of data and update data methods (string Boolean model class, update string update model class to get data), such a structure with the latest syntax generator should be how to achieve, Could the official provide a similar example in the document

@rrousselGit

laterdayi commented 7 months ago

a.controller file


class AController extends GetxController {
  // -------------------------------------------------------------------------------------------- > State & Controller
  static EngineerRepairController get to => Get.find();

  CallDetailModel callDetailData = CallDetailModel();
  List<FaultTypeListModel> faultTypeList = [];

  @override
  void onInit() async {
    super.onInit();
    handleGetCallDetail();
  }

  // -------------------------------------------------------------------------------------------- > Updater
  void updateCallDetail(CallDetailModel val) => callDetailData = val;
  void updateFaultType(List<FaultTypeListModel> val) => faultTypeList = val;

  // -------------------------------------------------------------------------------------------- > Action
  Future handleGetCallDetail() async {
    try {
      Map<String, dynamic> params = {'id': Get.arguments['id']};
      dynamic res = await HttpService.to.get(EngineerRepairApis.getCallDetailDataApi, params: params);
      updateCallDetail(CallDetailModel.fromJson(res));
      update();
    } catch (e) {
    }
  }}

This is how I define and update data in getx. Now I'm trying to switch to riverpod. How do I implement this in the latest riverpod generator

rrousselGit commented 7 months ago

You may be looking for https://riverpod.dev/docs/essentials/side_effects / https://github.com/rrousselGit/riverpod/blob/master/examples/todos/lib/todo.dart

laterdayi commented 7 months ago

This example looks like it only maintains todo data, and I have a lot of data in one controller, todolist, movieDetailData..... Do you want to create a provider for each data?

rrousselGit commented 7 months ago

Generally yes. Riverpod doesn't promote putting too much things into a single object

rrousselGit commented 7 months ago

To begin with, Riverpod isn't trying to respect MVC

laterdayi commented 7 months ago

How is this different from storing a lot of data in controller in getx? Is there a performance gap? Can you describe it in the documentation

herowws commented 7 months ago

首先,Riverpod 并没有试图尊重 MVC

The design of Riverpod leads to a significant amount of code duplication and a massive workload

shtse8 commented 7 months ago

I've reviewed the documentation multiple times but still have questions. Specifically, I'm unsure about monitoring state changes while accessing the notifier in a widget. The notifier offers many useful methods and properties, unlike the state. Also, I'm uncertain about the appropriate placement for utility objects such as streams, custom utilities, and caches. Should these be included in the notifier? Furthermore, I'm unclear about the optimal time for initializing these utilities. Would it be during the constructor or the build method? Additionally, I'm concerned about potential issues if the notifier is rebuilt.

rrousselGit commented 7 months ago

You're not supposed to "listen" to notifiers.All listenable state should be placed inside the "state" property

shtse8 commented 7 months ago

Thank you for your response. I'm still a bit unclear, though. Are you suggesting that all utilities, since they're not listenable, should be placed in the notifier? And for listenable utilities, should they be included in the state?

Additionally, I'm not actively listening to notifiers. In a relational model, we often need to access objects from other providers. I utilize conventional getters to fetch objects by ID from other providers, avoiding repetition. This is one reason I need to access the notifier while also listening to the state.

Could you please provide more insight on this, particularly regarding the optimal placement of utilities like streams, custom utilities, and caches? Should these be initialized in the constructor or the build method, and what are the implications if the notifier is rebuilt?

TekExplorer commented 7 months ago

all state in riverpod should be created and initialized in build (for notifiers) or the top-level function (in functional providers)

avoid storing any other state in your notifiers.

riverpod handles streams for you. simply set the return type to Stream<Foo> and you'll get an AsyncValue the same as Futures

If you want to get an object by it's id, add a parameter to build(int id) or function foo(FooRef ref, int id) which will create a family provider accessed with ref.watch(fooProvider(4));

this can be like:

@riverpod
List<Todo> todos(TodosRef ref) => []; // a list of todos

@riverpod
Todo todo(TodoRef ref, int id) { // a specific todo
  final todos = ref.watch(todosProvider);
  return todos.firstWhere((todo) => todo.id == id);
}
hamishjohnson commented 5 months ago

Having used riverpod + flutter_hooks since riverpod came out, you don't really need to do MVC. I use flutter_hooks for ephemeral state and riverpod for dependency injection + global state and it works a charm.

MannaYang commented 5 months ago

In my project , i use riverpod(v2.4.9) source code like this:

ChatGPT-Flutter-Web

The code call path : UI -> Provider -> Repository -> Http If the request is completed, the results are returned in order, you can control each stage, and handle state in Provider

/// Widget build() method 

final conversations = ref.watch(chatConversationsProvider);

chatConversationsProvider.dart


@riverpod
class ChatConversations extends _$ChatConversations {
///
/// Conversation List , Like Select\Insert\Update\Delete
///
@override
(int, String, List<ChatConversationInfo>?) build() =>
(StatusProvider.stateDefault, '', null);

void selectConversationList() async { var result = await ref .read(chatRepositoryProvider.notifier) .selectConversationList(); Logger().d("selectConversationList - $result"); state = (result.code, result.msg, result.data ?? []); } }

> chatRepositoryProvider.dart

@Riverpod(keepAlive: true) class ChatRepository extends _$ChatRepository { @override void build() {}

/// /// Get conversation list /// Future<BaseResult<List>> selectConversationList() async { var uri = Uri.http(ApiProvider.backendUrl, 'api/llm/v1/conversation/list'); try { final response = await ref.read(httpRequestProvider.notifier).get(uri); var json = jsonDecode(utf8.decode(response.bodyBytes)); var data = BaseResult<List>.fromJson( json, (jsonData) => (jsonData as List) .map((item) => ChatConversationInfo.fromJson(item)) .toList()); return data; } catch (e) { return BaseResult( code: -1, msg: e.toString(), success: false, data: null); } } }


> httpRequestProvider.dart

@Riverpod(keepAlive: true) class HttpRequest extends _$HttpRequest { @override void build() {}

/// /// http - get /// Future get( Uri url, { Map<String, String>? headers, int timeout = _reqTime, }) { Logger().d("HttpProvider - Request = $url"); var response = http.get(url, headers: _tokenHeader()).timeout(_timeout(timeout)); return _handleResponse(response); } }

login3b7e commented 3 months ago

Is above example by @MannaYang a recommended pattern for the current state of Riverpod?

I've used 1.x versions and am used to creating a provider that passes in a Ref to a repository class for example, which then uses the ref to access a (REST API) client exposed through another provider; this seems very similar.

Is it still recommended to work like this? It's basically as in above chatRepositoryProvider.dart and httpRequestProvider.dart which have a void state.

If not the recommended pattern, what is the suggested approach? E.g. have an annotated provider for all methods such as selectConversationList() for example, which would generate a FutureProvider?

Thanks in advance, trying to wrap my head around the changes 1.x to 2.x + code generation.

TekExplorer commented 2 weeks ago

do not have a void provider. that is completely pointless. instead, have an actual normal repository class, and provide that in a functional provider

Class provider methods should mutate the state. otherwise, why have it?