Giter Club home page Giter Club logo

shadyboukhary / flutter_clean_architecture Goto Github PK

View Code? Open in Web Editor NEW
660.0 660.0 168.0 304 KB

Clean architecture flutter: A Flutter package that makes it easy and intuitive to implement Uncle Bob's Clean Architecture in Flutter. This package provides basic classes that are tuned to work with Flutter and are designed according to the Clean Architecture.

Home Page: https://pub.dartlang.org/packages/flutter_clean_architecture

License: MIT License

Dart 97.64% Java 0.17% Objective-C 0.91% Shell 0.69% HTML 0.60%
clean-architecture clean-code dart dart2 dartlang design-pattern flutter flutter-package hacktoberfest uncle-bob

flutter_clean_architecture's People

Contributors

bssughosh avatar ccfiel avatar daddelbob avatar darkmat13r avatar irayspace avatar jacobggman avatar jediburrell avatar rafaelcmm avatar shadyboukhary avatar tp avatar williamcunhacardoso avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

flutter_clean_architecture's Issues

More example for background task

Is your feature request related to a problem? Please describe.
Thank you for the work accomplished. It is a precious help. But I have a problem with your example background task use case.
Put questions are how do we get the answer and how do we send the parameters from the presenter?
How can I use ?

Cant add AppBar via ControlledWidgetBuilder.builder

Have error if add App Bar via ControlledWidgetBuilder.builder.

final result = Scaffold(

 appBar: ControlledWidgetBuilder<CategoryGroupListController>(
          builder: (context, controller) {
            return BaseAppBar(title: controller.title);
          },
        ),
        body: columnContainer
    );

Error log:

error: The argument type 'ControlledWidgetBuilder<CategoryGroupListController>' can't be assigned to the parameter type 'PreferredSizeWidget'. (argument_type_not_assignable at [project_name] lib/project_name/fca/app/pages/category/category_group_list/category_group_list_view.dart:62)

Please describe how resolve this problem?

controller.isLoading is now undefined

In version 3.0.1 controller.isLoading is now undefined. What would be the replacement for this? ๐Ÿ˜Š @ShadyBoukhary

class CategoryDetailView extends View {
  final int id;

  CategoryDetailView({Key key, this.id}) : super(key: key);

  @override
  _CategoryDetailViewState createState() => _CategoryDetailViewState(this.id);
}

class _CategoryDetailViewState
    extends ViewState<CategoryDetailView, CategoryDetailController> {
  _CategoryDetailViewState(id)
      : super(CategoryDetailController(DataCategoriesRepository(), id: id));
  @override
  Widget buildPage() {
    return Scaffold(
      key: globalKey,
      appBar: AppBar(
        title: Text(translate('categories.detail')),
        actions: <Widget>[
          controller.id != null
              ? IconButton(
                  icon: Icon(Icons.delete),
                  onPressed: controller.deleteCategory,
                )
              : Container(),
        ],
      ),
      body: Container(
        child: SingleChildScrollView(
          child: Loading(
            loading: controller.isLoading, <----- =========== is not defined ===============
            child: CategoryForm(controller: controller.formController),
          ),
        ),
      ),
    );
  }
}

RefreshUI and setState

First of all, thank you for the library and spreading the Clean Architecture concept.
It is unclear for me what should I do when I need UI to react to the change of a variable.
For example the isLoading flag is triggered when a network call is executed, should I call the refreshUI method every time? Does this approach have any side effects related to performance?

I've also tried the following code:

this.getState().setState(()=> {})

But this throws an exception due to an uninitialised variable.

Please clarify what is the right way to update UI state?

Thank you.

Implement didViewChangeDependencies

There is initViewState(Controller controller) which is an approach for using initState with controller. But there is nothing to didChangeDependencies

So, at least for ResponsiveView, there should be a didViewChangeDependencies(Controller controller)

Question about placement of Facebook login logic in a repository

Hi, I implementing a login flow in my application, using the clean architecture, but I have a doubt about which location I put the facebook login logic.

I'm using the flutter_facebook_login package, and I have doubts if I should put the login logic in the controller, presenter or repository.

My basic logic is that:

final FacebookLogin facebookLogin = FacebookLogin();
FacebookLoginResult result = await facebookLogin.logInWithReadPermissions(<String>['email']);

if(result.status == FacebookLoginStatus.error) {
   facebookLogin.logOut();
   result = await facebookLogin.logInWithReadPermissions(<String>['email']);
}

return result;

I think it's a simple question, but I was pretty confused about it.

Thank you,
Andrei J. Zuse

Error: Getter not found: 'suspending'.

@ShadyBoukhary when running flutter run the package has an error.

~/Projects/todo$ flutter run
Using hardware rendering with device AOSP on IA Emulator. If you get graphics artifacts, consider enabling software rendering with "--enable-software-rendering".
Launching lib/main.dart on AOSP on IA Emulator in debug mode...
[!] Your app isn't using AndroidX.
    To avoid potential build failures, you can quickly migrate your app by following the steps on https://goo.gl/CP92wY.
Checking the license for package CMake 3.6.4111459 in /home/chris/Android/Sdk/licenses
License for package CMake 3.6.4111459 accepted.                         
Preparing "Install CMake 3.6.4111459 (revision: 3.6.4111459)".          
"Install CMake 3.6.4111459 (revision: 3.6.4111459)" ready.              
Installing CMake 3.6.4111459 in /home/chris/Android/Sdk/cmake/3.6.4111459
"Install CMake 3.6.4111459 (revision: 3.6.4111459)" complete.           
"Install CMake 3.6.4111459 (revision: 3.6.4111459)" finished.           
                                                                        
Compiler message:                                                       
../../flutter/.pub-cache/hosted/pub.dartlang.org/flutter_clean_architecture-1.0.5/lib/src/controller.dart:105:30: Error: Getter not found: 'suspending'.
      case AppLifecycleState.suspending:                                
                             ^^^^^^^^^^                                 
Target kernel_snapshot failed: Exception: Errors during snapshot creation: null
build failed.                                                           
                                                                        
FAILURE: Build failed with an exception.             

when I tried to look in the flutter documentation there no suspending state only detached, paused, inactive and resumed
https://api.flutter.dev/flutter/dart-ui/AppLifecycleState-class.html

Flow of control question

Hello! It's a great work.

I try to implement the clean architecture follow your repo.
But i have a little confuse about the data flow. I checked your comment in another question and now the flow is
UI -> Controller -> Presenter -> UseCase -> Repository

But from Uncle Bob original design : https://blog.cleancoder.com/uncle-bob/images/2012-08-13-the-clean-architecture/CleanArchitecture.jpg
The data flow as lower right will be:
Controller -> User case -> Presenter
So i think the controller should call usercase (i'm so confuse about this point). But in repo the presenter call usercase.excute(...).

So i just want to know more about this: what is the advantages/disadvantages when we implement as Uncle Bob original version(if i wrong about Uncle Bob diagram, please correct me).
Again, your work is awesome!
Many thanks!

refreshUI() doesn't work with Shared Controller.

Description

Firstly, thank you for this project. It's really useful in my project.
Coming to the issue, I am trying to implement a shared controller and presenter with multiple views.
Each view has its own controller and presenter as well.
I had referred to an earlier issue and the example provided as well:
#15

I have created a shared controller and presenter.
It is initialized in the widget tree above the other views and called further down in the tree where required.
The data is accessible and consumed by the widget.
However if I call refreshUI() in the shared controller to update data in the shared controller, the view does not get updated.

Code

Shared Controller
import 'package:flutter_clean_architecture/flutter_clean_architecture.dart';

class SharedController extends Controller {
  final BookmarkManager _bookmarkManager = BookmarkManager();
  final WatchlistManager _watchlistManager = WatchlistManager();

  List<dynamic>? _data = [];
  List<dynamic> _suggestions = [];
  
  Map<String, List<dynamic>> _overview = {};
  
  List<StockSuggestion> get suggestions => _suggestions;
  Map<String, List<StockData>> get overview => _overview;

  set overview(Map<String, List<StockData>> data) {
    _overview.addAll(data);
    refreshUI();
  }
  
  SharedController() : super();

  @override
  void initListeners() {
    _bookmarkManager.getBookMarkList().then((value) => _data = value);
    _watchlistManager.getWatchlists().then((value) => _watchlistTabs = value);
  }


  @mustCallSuper
  @override
  void onDisposed() {
    super.onDisposed();
  }

 updateSuggestions() async {
    /* API call for getting suggestions */
    var result = async ApiCallResult();
    /* Update suggestion */
    _suggestions = result;

   refreshUI();        <-- **Does not execute**
  }
}
Parent Widget
import 'package:example/app/pages/shared/shared_controller.dart';
import 'package:example/app/widgets/form/route_form.dart';
import 'package:flutter/material.dart';
import 'package:flutter_clean_architecture/flutter_clean_architecture.dart';

class RoutePage extends View {
  static _RouteState of(BuildContext context) =>
      context.findAncestorStateOfType<_RouteState>()!;

  @override
  _RouteState createState() => _RouteState();
}

class _RouteState extends ViewState<RoutePage, SharedController> {
  _RouteState() : super(SharedController());

  @override
  Widget get view {
    return ControlledWidgetBuilder<SharedController>(builder: (_, controller) {
      return RouteForm();
    });
  }
}

Child Widget
import 'package:example/app/pages/shared/shared_controller.dart';
import 'package:example/app/widgets/card/overview_card.dart';
import 'package:flutter/material.dart';
import 'package:flutter_clean_architecture/flutter_clean_architecture.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';

class OverviewSlider extends StatelessWidget {
  const OverviewSlider({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return ControlledWidgetBuilder<SharedController>(
      builder: (_, controller) {
        return Container(
          height: 350.sp,
          child: Center(
            child: ListView.builder(
              scrollDirection: Axis.horizontal,
              itemBuilder: (_, index) {
                return OverviewCard(
                    controller: controller,
                    suggestion: controller.suggestions[index],
                    showCurrency: false);
              },
              itemCount: controller.suggestions.length,
            ),
          ),
        );
      },
    );
  }
}

On calling the updateSuggestions() method, the data is fetched and the _suggestions variable is updated.
However, the UI does not update.

Any help would be appreciated.

Library Version

  • flutter_clean_architecture: ^5.0.0

How to implement unit test for use cases

I want to create unit test for use case of the sample todo app. Any idea how? @ShadyBoukhary

class AddTodoUseCase
    extends UseCase<AddTodoUseCaseResponse, AddTodoUseCaseParams> {
  TodosRepository todosRepository;

  AddTodoUseCase(this.todosRepository);

  @override
  Future<Observable<AddTodoUseCaseResponse>> buildUseCaseObservable(
      AddTodoUseCaseParams params) async {
    final StreamController<AddTodoUseCaseResponse> controller =
        StreamController();
    try {
      if (params.todo.title == null || params.todo.title == "") {
        throw 'Title should not be blank!';
      } else {
        params.todo.id = todosRepository.todosLength;
        params.todo.completed = false;
        todosRepository.addTodo(params.todo);
        controller.add(AddTodoUseCaseResponse(params.todo));
        logger.finest('AddTodoUseCase successful.');
      }
      controller.close();
    } catch (e) {
      logger.severe('AddTodoUseCase unsuccessful.');
      controller.addError(e);
    }
    return Observable(controller.stream);
  }
}

/// Wrapping params inside an object makes it easier to change later
class AddTodoUseCaseParams {
  final Todo todo;

  AddTodoUseCaseParams(this.todo);
}

/// Wrapping response inside an object makes it easier to change later
class AddTodoUseCaseResponse {
  final Todo todo;

  AddTodoUseCaseResponse(this.todo);
}```

Hot reload

When I execute hot reload, all events will be executed twice

log๏ผš
2021-01-27 20:56:27.049 18460-18565 I/flutter: _TabLayoutViewState: INFO: _TabLayoutViewState reassemble
2021-01-27 20:56:27.049 18460-18565 I/flutter: _TabLayoutViewState: INFO: _TabLayoutViewState reassemble
2021-01-27 20:56:27.053 18460-18565 I/flutter: _MatchPageState: INFO: _MatchPageState reassemble
2021-01-27 20:56:27.054 18460-18565 I/flutter: _MatchPageState: INFO: _MatchPageState reassemble
2021-01-27 20:56:27.090 18460-18565 I/flutter: : INFO: Logger initialized.
2021-01-27 20:56:27.090 18460-18565 I/flutter: : INFO: Logger initialized.
2021-01-27 20:56:27.100 18460-18565 I/flutter: _TabLayoutViewState: INFO: _TabLayoutViewState deactivate
2021-01-27 20:56:27.100 18460-18565 I/flutter: _TabLayoutViewState: INFO: _TabLayoutViewState deactivate
2021-01-27 20:56:27.101 18460-18565 I/flutter: _MatchPageState: INFO: _MatchPageState deactivate
2021-01-27 20:56:27.101 18460-18565 I/flutter: _MatchPageState: INFO: _MatchPageState deactivate
2021-01-27 20:56:27.123 18460-18565 I/flutter: _MatchPageState: INFO: Disposing _MatchPageState.
2021-01-27 20:56:27.123 18460-18565 I/flutter: _MatchPageState: INFO: Disposing _MatchPageState.
2021-01-27 20:56:27.123 18460-18565 I/flutter: MatchChannelUsecase: INFO: Disposing MatchChannelUsecase
2021-01-27 20:56:27.124 18460-18565 I/flutter: MatchChannelUsecase: INFO: Disposing MatchChannelUsecase
2021-01-27 20:56:27.124 18460-18565 I/flutter: MatchPageController: INFO: Disposing MatchPageController
2021-01-27 20:56:27.124 18460-18565 I/flutter: MatchPageController: INFO: Disposing MatchPageController
2021-01-27 20:56:27.134 18460-18565 I/flutter: _TabLayoutViewState: INFO: Disposing _TabLayoutViewState.
2021-01-27 20:56:27.134 18460-18565 I/flutter: _TabLayoutViewState: INFO: Disposing _TabLayoutViewState.
2021-01-27 20:56:27.135 18460-18565 I/flutter: GetBottomNavItemsUsercase: INFO: Disposing GetBottomNavItemsUsercase
2021-01-27 20:56:27.135 18460-18565 I/flutter: GetBottomNavItemsUsercase: INFO: Disposing GetBottomNavItemsUsercase
2021-01-27 20:56:27.135 18460-18565 I/flutter: TabLayoutViewController: INFO: Disposing TabLayoutViewController
2021-01-27 20:56:27.135 18460-18565 I/flutter: TabLayoutViewController: INFO: Disposing TabLayoutViewController
2021-01-27 20:56:27.147 18460-18565 I/flutter: apiEnv: ApiEnv.release
2021-01-27 20:56:27.211 18460-18565 I/flutter: MainPage build
2021-01-27 20:56:27.212 18460-18565 I/flutter: _TabLayoutViewState: INFO: _TabLayoutViewState initState
2021-01-27 20:56:27.212 18460-18565 I/flutter: _TabLayoutViewState: INFO: _TabLayoutViewState initState
2021-01-27 20:56:27.212 18460-18565 I/flutter: _TabLayoutViewState: INFO: _TabLayoutViewState didChangeDependencies triggered
2021-01-27 20:56:27.212 18460-18565 I/flutter: _TabLayoutViewState: INFO: _TabLayoutViewState didChangeDependencies triggered
2021-01-27 20:56:27.213 18460-18565 I/flutter: _TabLayoutViewState: INFO: ViewState build
2021-01-27 20:56:27.213 18460-18565 I/flutter: _TabLayoutViewState: INFO: ViewState build
2021-01-27 20:56:27.213 18460-18565 I/flutter: _TabLayoutViewState: INFO: MainPage TabLayoutView getview
2021-01-27 20:56:27.213 18460-18565 I/flutter: _TabLayoutViewState: INFO: MainPage TabLayoutView getview

Error when migrating from flutter_clean_architecture ^3.0.2 to ^5.0.0

I want to upgrade my package version from version 3.0.2 to 5.0.0.

I noticed the view getter changed so I performed the following change to my codebase:

@override
  Widget get view => buildPage();

  // removed the override modifier here
  Widget buildPage() {
      //....
  }

I had access to the controller variable in my views.
Now that I don't have it I retrieve it using:
var controller = FlutterCleanArchitecture.getController<HomeController>(context);

But when running my application I am getting the following error:
"Error: Could not find the correct Provider above this MovesListingRoute Widget

This happens because you used a 'BuildContext' that does not include the provider of your choice."

Is there some breaking change from those two versions ?

Question: Same controller in multiple widgets

Hey,
thanks for this project.
I started refactoring my application and I use a Form Widget which I use in multiple pages. My current implementation is the following:

The pages

History page

class HistoryPage extends View {
  @override
  State<StatefulWidget> createState() => HistoryState();
}

class HistoryState extends ViewState<HistoryPage, HistoryController> {
  HistoryState() : super(HistoryController(DataReceiptRepository()));

...

Home page


class HomePage extends View {
  @override
  State<StatefulWidget> createState() => _HomePageState();
}

class _HomePageState extends ViewState<HomePage, HomeController> {
  _HomePageState() : super(HomeController(DataReceiptRepository()));

...

The form

/class InputForm extends StatelessWidget {
  final bool homeWidget;

  const InputForm({Key? key, required this.homeWidget}) : super(key: key);

  Widget storeNameTextField(BuildContext context, HomeController controller) =>
      PaddingWidget(
          widget: SimpleTextfieldWidget(
              controller: controller.storeNameController,
              hintText: "Store Name",
              labelText: "Store Name",
              helperText: "The receipt store name",
              validator: controller.validateStoreName,
              suggestionList: controller.getStoreNames(),
              readOnly: false,
              icon: Icon(Icons.store_mall_directory_outlined)));

  Widget tagTextField(BuildContext context, HomeController controller) =>
      PaddingWidget(
          widget: SimpleTextfieldWidget(
        controller: controller.receiptTagController,
        hintText: "Receipt Tag",
        labelText: "Receipt Tag",
        helperText: "The receipt tag",
        validator: (value) => null,
        suggestionList: controller.getTagNames(),
        icon: Icon(Icons.tag),
        readOnly: false,
      ));

  Widget totalTextField(BuildContext context, HomeController controller) =>
      PaddingWidget(
          widget: Row(
        mainAxisAlignment: MainAxisAlignment.center,
        crossAxisAlignment: CrossAxisAlignment.center,
        children: [
          Expanded(
              child: SimpleTextfieldWidget(
            controller: controller.receiptTotalController,
            hintText: "Receipt Total",
            labelText: "Receipt Total",
            helperText: "The receipt total",
            icon: Icon(Icons.monetization_on_outlined),
            validator: controller.validateTotal,
            inputFormatters: [MoneyInputFormatter()],
            keyboardType: TextInputType.number,
            readOnly: false,
          )),
          PaddingWidget(
            widget: NeumorphicButton(
              onPressed: () => controller.currencyPicker(context),
              style: NeumorphicStyle(
                  color: Colors.grey[200],
                  shape: NeumorphicShape.flat,
                  boxShape: NeumorphicBoxShape.stadium(),
                  border: NeumorphicBorder(width: 2, color: Colors.green)),
              child: Padding(
                  padding: const EdgeInsets.all(4.0),
                  child: Text(
                    controller.currency?.code ?? "EUR",
                    style: TextStyle(fontWeight: FontWeight.bold),
                  )),
            ),
          )
        ],
      ));

  Widget dateTextField(BuildContext context, HomeController controller) =>
      PaddingWidget(
          widget: GestureDetector(
              onTap: () => controller.setDate,
              child: SimpleTextfieldWidget(
                controller: controller.receiptDateController,
                hintText: "Receipt Date",
                labelText: "Receipt Date",
                helperText: "The receipt date",
                onTap: () => controller.setDate(context),
                icon: Icon(Icons.date_range),
                validator: controller.validateDate,
                readOnly: true,
              )));

  Widget submitButton(BuildContext context, HomeController controller) =>
      PaddingWidget(
          widget: Align(
        alignment: Alignment.centerLeft,
        child: NeumorphicButton(
            onPressed: () => controller.submit(),
            style: NeumorphicStyle(
              shape: NeumorphicShape.flat,
              boxShape: NeumorphicBoxShape.stadium(),
            ),
            child:
                Text("Submit", style: TextStyle(fontWeight: FontWeight.bold))),
      ));

  Widget categoryTextFormat(BuildContext context, HomeController controller) =>
      PaddingWidget(
          widget: SimpleTextfieldWidget(
        controller: controller.receiptCategoryController,
        hintText: "Receipt Category",
        labelText: "Receipt Category",
        helperText: "The receipt category",
        icon: Icon(Icons.category),
        validator: controller.validateCategory,
        suggestionList: controller.getCategoryNames(),
        readOnly: false,
      ));

  spacer() => SizedBox(
        height: 50,
      );

  @override
  Widget build(BuildContext context) {
    HomeController controller =
        FlutterCleanArchitecture.getController<HomeController>(context);

    return SingleChildScrollView(
        child: Form(
      key: controller.formKey,
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          storeNameTextField(context, controller),
          totalTextField(context, controller),
          dateTextField(context, controller),
          tagTextField(context, controller),
          categoryTextFormat(context, controller),
          submitButton(context, controller),
          spacer()
        ],
      ),
    ));
  }
}

However, if call my edit method (defined in the HistoryController):

 void editMethod(ReceiptHolder receipt, BuildContext context) async {
    await showDialog<String>(
        context: context,
        builder: (BuildContext dialogContext) {
          return AlertDialog(
              titleTextStyle: TextStyle(
                  color: Colors.black,
                  fontWeight: FontWeight.w400,
                  fontSize: 22),
              backgroundColor: Colors.white,
              title: Text("Edit receipt"),
              content: Container(
                  height: 300,
                  width: 250,
                  color: Colors.white,
                  // The Form widget earlier
                  child: InputForm(
                    homeWidget: false,
                  )));
        });
    await repository.updateReceipt(receipt);
  }

I receive following error:

Error: Could not find the correct Provider<HomeController> above this InputForm Widget

This happens because you used a `BuildContext` that does not include the provider
of your choice. 

How could I fix this issue. I understand the issue but I'm not able to resolve this issue. Thanks in advance!

William

Add listen param to getController

Describe the bug
I was testing a controller call outside build and I was facing a provider error. It was requesting me to use listen false on provider call method, but getController does not provide this to me. So I decided to PR it

To Reproduce
Steps to reproduce the behavior:

  1. Call getController outside build method in a stateful widget

Expected behavior
Provider error about listen not being false

Screenshots
Screenshot from 2020-09-21 12-54-34
Screenshot from 2020-09-21 12-52-36

Library (please complete the following information):

  • flutter_clean_architecture: ^3.1.0

Smartphone (please complete the following information):

  • Device: [Pixel Emulator]
  • OS: [Android]
  • Version [9]

Additional context
It is a simple optional param to add. I am doing a pr to it

Problem with AutomaticKeepAliveClientMixin

Please tell me how to keep state of view in a tabbar or navigation bottom bar, thank you. I try to implement with AutomaticKeepAliveClientMixin but it show error:

'To return an empty space that causes the building widget to fill available room, return "Container()". To return an empty space that takes as little room as possible, return "Container(width: 0.0, height: 0.0)".'

One Controller for multiple pages

My issue is simple

I have one controller that will serve three or more different views, when I navigate between pages the values in the controller are reset! has anyone been able to achieve the above? if yes, how?

Extending an base usecase, complexity vs consistency

Because you became a source of inspiration :), I looked over you code from https://github.com/ShadyBoukhary/Axion-Technologies-HnH (development branch) and I saw that you don't extends a base use case. Do you think is really necessary to extend a base usecase witch adds more complexity? For returning a bool to have to involve streams? (like in login case) #3 (comment)


On another examples out there I saw something done with dartz package, Also to much, I don;t always want to return a future and what about Type being a stream???

abstract class UseCase<Type, Params> {
  Future<Either<Failure, Type>> call(Params params);
}


Also creating more abstract usecases classes looks a bit silly.
Because dart classes are already interfaces we can use this in testing and avoid using a base usecase and use stream base use case when we are dealing with streams.
Can you explain a bit about this motivation on using this base usecase everywhere?

Thanks!

[Question] How do I write test for the controller or presenter class?

Hi there,

As I couldn't found any code example, hence I created this issue to raise my question.

I not sure whether it is more appropriate to write tests for the controller class or the presenter class.
Appreciate if you can share a code example on how to do so.

Thank you.

clarification: globalKey usage

Currently, I am using flutter_clean_architecture for our mobile app. We have a scenario where we have to use a SnackBar to display error under controller.initListeners().

Our current implementation under controller.initListeners() is as follow:

getStateKey().currentState.showSnackBar(
  SnackBar(
    backgroundColor: Colors.red,
    content: Text(errors['internal_error']),
  )
);

In order to use the SnackBar, we temporarily set State<StatefulWidget> to ScaffoldState.

Are there any other scenarios where globalKey is used aside from it is GlobalKey for Scaffold?

Regards,

Ivan

[Question] Flow of presentation layer

First of thanks for creating this package! It gave us a lot of food for thought about architecture.

I have some remarks and I am really interested in your point of view on the matter. I seem to have a different interpretation of the View Controller Presenter flow based of your package.

To quote some parts from the original blog by Uncle Bob about the "presentation" layer:

https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html#interface-adapters

[...]
It is this layer, for example, that will wholly contain the MVC architecture of a GUI.
The Presenters,Views, and Controllers all belong in here. The models are likely just
data structures that are passed from the controllers to the use cases, and then back
from the use cases to the presenters
and views.
[...]

https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html#crossing-boundaries

[...]
It shows the Controllers and Presenters communicating with the Use Cases in the next layer.
Note the flow of control. It begins in the controller, moves through the use case, and
then winds up executing in the presenter.
[...]

So I would expect that an action in the view has following flow: View => Controller => Usecase => Presenter => View

With your implementation the flow is like this: View => Controller => Presenter => Usecase => (Presenter) => Controller => View

Could you shed some light on this?

Controller onResumed: assertion failed context != null

Good day!

We are incorporating passcode input into our mobile application. However, we wanted to have a protected page where the user paused and goes back to the application (onResumed triggered) the controller will navigate to pin route.

The current code implementation goes like this.

@override
void onResumed() {
  Navigator.of(getContext()).pushReplacementNamed('/pin');
}

However, when we do the user paused and goes back to the application the second time, we encounter errors related to global keys.

โ•โ•โ•โ•โ•โ•โ•โ• Exception caught by services library โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
The following assertion was thrown during a platform message callback:
Make sure you are using the `globalKey` that is built into the `ViewState` inside your `build()` method.
        For example:
        `key: globalKey,` Otherwise, there is no context that the `Controller` could access.
        If this does not solve the issue, please open an issue at `https://github.com/ShadyBoukhary/flutter_clean_architecture` describing 
     the error.
'package:flutter_clean_architecture/src/controller.dart':
Failed assertion: line 212 pos 12: '_globalKey.currentContext != null'


Either the assertion indicates an error in the framework itself, or we should provide substantially more information in this error message to help you determine and fix the underlying cause.
In either case, please report this assertion by filing a bug on GitHub:
  https://github.com/flutter/flutter/issues/new?template=BUG.md

When the exception was thrown, this was the stack: 
#2      Controller.getContext (package:flutter_clean_architecture/src/controller.dart:212:12)
#3      UsersController.onResumed (package:tailpos/app/pages/users/users_controller.dart:42:18)
#4      Controller.didChangeAppLifecycleState (package:flutter_clean_architecture/src/controller.dart:103:9)
#5      WidgetsBinding.handleAppLifecycleStateChanged (package:flutter/src/widgets/binding.dart:545:16)
#6      SchedulerBinding._handleLifecycleMessage (package:flutter/src/scheduler/binding.dart:346:5)
...
โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

Wondering what is your take on this.

Regards,

Ivan

How to avoid rebuild in components.

1.How can we void extra build in components like in case of our example we are using ElevatedButton to fire just getuser
while we do that it gets rebuild. Can we build a another builder that only trigger but not build subtree under it?

How to get controller in v4

Hi, nice work here.

One question, how to get controller getter in v4 since it was removed? I want to get the controller in initState of view.

Flutter web compatibility

I'm new to flutter development and generally to dart and web development.
I read the introduction of your component and it look really nice as basic flutter development can lead to caos!

I would like to know if the framework can be used on flutter web? For web app development?
Or if is planned to be ported?

Thanks.

Unable to override `initState` on ViewState

Describe the bug
I am unable to override initState in ViewState to properlly handle animation.

To Reproduce
Steps to reproduce the behavior:

  1. Create an implementation of ViewState likes it is done here https://github.com/ShadyBoukhary/Axion-Technologies-HnH/blob/development/lib/app/pages/splash/splash_view.dart#L24
  2. Override the method initState
  3. This message is displayed: The member 'initState' is declared non-virtual in 'ViewState' and can't be overridden in subclasses.โ€‹

Expected behavior
I should be able to override initState.

Library (please complete the following information):

  • Version [5.0.0]

Additional context
Add any other context about the problem here.

In view.dart on the method initState should not be @nonVirtual. It should be @mustCallSuper.

Create CLI

Could we create a CLI like Angular to facilitate the creation of files?

Web support for BackgroundUseCase

I think it is a waste when flutter developers walk away from the flutter_clean_architecture package due to many think that it doesn't support web.

I just come across the following package which might enable background processing support in web:
https://pub.dev/packages/worker_manager

Hopefully, we'll see full web support for the flutter_clean_architecture package soon.

Good Friday! :)

[enhancement] Controller's refreshUI() function when called reloads the whole widget tree.

First of all I would like to thank you for creating such a nice plugin. It really helped me to implement a good architecture in my projects.

It seems that this plugin uses Provider for the state management of the application. But one thing I have noticed that, when I call the refreshUI function from the Controller, the whole widget tree gets reloaded. I have used BLoC pattern earlier where using Streams we can update only the portion of the UI which is required to update. The same is also possible using Provider.

Can you please help me to understand how can we achieve the same using the Controller of the plugin? Also please let me know if I have missed on something.

Thanks in advance.

Update provider and rxdart package versions

Can we update the packages => provider and rxdart because it gives the following errors since I am working with the newest versions of both.

Because flutter_clean_architecture >=1.0.3 depends on provider ^2.0.1+1 and flutter_clean_architecture <1.0.3 depends on rxdart ^0.20.0, every version of flutter_clean_architecture requires provider ^2.0.1+1 or rxdart ^0.20.0.
And because myproject depends on provider ^3.0.0, every version of flutter_clean_architecture requires rxdart ^0.20.0.
So, because myproject depends on both rxdart ^0.22.0 and flutter_clean_architecture ^1.0.1, version solving failed.

pub get failed (1)

Is there any dependency issue if the two packages are updated to their latest versions?
The maintenance of your package is also affected.

Can u update to support flutter_bloc >=7.2.0

Because flutter_bloc >=7.2.0 depends on provider ^6.0.0 and flutter_clean_architecture 5.0.0 depends on provider ^5.0.0, flutter_bloc >=7.2.0 is incompatible with flutter_clean_architecture 5.0.0.
And because no versions of flutter_clean_architecture match >5.0.0 <6.0.0, flutter_bloc >=7.2.0 is incompatible with flutter_clean_architecture ^5.0.0.
So, because balance3d depends on both flutter_clean_architecture ^5.0.0 and flutter_bloc ^7.3.1, version solving failed.
pub get failed (1; So, because balance3d depends on both flutter_clean_architecture ^5.0.0 and flutter_bloc ^7.3.1, version solving failed.)

README typo

In the Example Code Section of the README.md
In the CounterPage class, the CounterState is being constructed with CounterState.

I'm pretty sure it is CounterController .

Provider outdated

Saw on your pubspec.yaml that provider is outdated.
Your import: provider: ^4.0.1
provider package: provider 4.3.2+3

Feature: Background thread UseCases

Overview

It would be useful to implement a UseCase that runs on a background thread. Perhaps this task would comprised of complex mathematical operations that are not suitable to be executed on the main thread.

Methods

The most straight forward way to implement this is to use Dart's Isolate class and subclass UseCase.
An abstraction over the isolate would be immensely useful, as it seems that the setup with the Observer + RxDart works nicely with Isolate's callback funcitons.

Usage

The user would be able to extend BackgroundUseCase or perhaps even a CompletableBackgroundUseCase and simply write the background logic inside buildUseCaseObservable().

If try add block isolate I have error

Because every version of isolate_bloc depends on provider ^4.3.1 and flutter_clean_architecture 5.0.0 depends on provider ^5.0.0, isolate_bloc is incompatible with flutter_clean_architecture 5.0.0.
And because no versions of flutter_clean_architecture match >5.0.0 <6.0.0, isolate_bloc is incompatible with flutter_clean_architecture ^5.0.0.
So, because be_logist depends on both flutter_clean_architecture ^5.0.0 and isolate_bloc ^1.0.4, version solving failed.
pub get failed (1; So, because be_logist depends on both flutter_clean_architecture ^5.0.0 and isolate_bloc ^1.0.4, version solving failed.)

Controller onResumed: assertion failed context != null

When onResumed() is triggered after using Navigator.of(getContext().pushNamed(...), the _globalKey.currentContext is `null.

I/flutter ( 6121): โ•โ•โ•ก EXCEPTION CAUGHT BY SERVICES LIBRARY โ•žโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
I/flutter ( 6121): The following assertion was thrown during a platform message callback:
I/flutter ( 6121): Make sure you are using the `globalKey` that is built into the `ViewState` inside your `build()`
I/flutter ( 6121): method.
I/flutter ( 6121):         For example:
I/flutter ( 6121):         `key: globalKey,` Otherwise, there is no context that the `Controller` could access.
I/flutter ( 6121):         If this does not solve the issue, please open an issue at
I/flutter ( 6121): `https://github.com/ShadyBoukhary/flutter_clean_architecture` describing
I/flutter ( 6121):      the error.
I/flutter ( 6121): 'package:flutter_clean_architecture/src/controller.dart':
I/flutter ( 6121): Failed assertion: line 173 pos 12: '_globalKey.currentContext != null'

Expected behavior
Should be that after route navigation, getContext() should be triggered.

GIF
currentContext Problem

Environment

  • Flutter (Channel stable, v1.12.13+hotfix.5, on Linux, locale en_US.UTF-8)
  • Version 3.0.1
  • Android Version 8.1.0

how we can handle, if it is multiple usecase called?

in HomePresenter have 3 methods declaration like Function getNext; Function getDone : Function getError;
in case presenter have more than one usecase call, what we going todo? are we going to declare multiple methods like above mentioned.
Kindly explain about that.

Using and placement of Server Auth/Firebase Auth

I am implementing clean architecture and I am not sure where all fits in. It seems to me that you got it all :) about this clean(gonoid) architecture.

For auth I have two possibilities:

  1. Server Auth.
    After use case login I get a token, with this token I will do all the http request that need authorization. Token is received back from usecase->presenter->controller
    a) Should this token be stored into ApiService class (single instance using service locator get_it) ?
    This ApiService class should reside in Data and added as dependency to other repositories (almost all repositories will need it, as all http request will use this token).
    b) All usecases->repo(http requests using the token) can receive 401 http error, this means that all the controllers should know how to handle this error, 401. Can this get handled in base controller? The controller in this case should navigate, route you back to login page

  2. Firebase Auth.
    a) Should I create an adapter for firebase auth, FirebaseService that calls methods on FirebaseAuh like login, signOut, getUser...?
    Future<FirebaseUser> currentUser() { return _firebaseAuth.currentUser(); //_firebaseAuth is FirebaseAuth.instance }
    b) Should I use this class FirebaseService as dependency to repositories, when is neded (get the userId, etc...) ?
    c) FirebaseService should stay in Data part of the clean architecture?
    d) FirebaseAuth auth can be used as a stream when changes to auth occurs (FirebaseAuth.instance.onAuthStateChanged). How can I use this ? directly in controller? or follow the path controller->presenter->usecase->firebseRepo get back the stream and listen to it on the top app controller.

Thanks!

Web support for BackgroundUseCase

I think it is a waste when flutter developers walk away from the flutter_clean_architecture package due to many think that it doesn't support web.

I just come across the following package which might enable background processing support in web:
https://pub.dev/packages/worker_manager

Hopefully, we'll see full web support for the flutter_clean_architecture package soon.

Good Friday! :)

Controller in PreferredSizeWidget

Is there a way to access the controller from the AppBar? It is not possible to use ControlledWidgetBuilder as it returns a Widget and AppBar requires a PreferredSizeWidget

Example:

AppBar get appBar => AppBar(
        title: Text("Title"),
        backgroundColor: Colors.white,
);

Valid case:

AppBar get appBar => AppBar(
        title: ControlledWidgetBuilder<Controller>(builder: (context, controller) {
              return Text(controller.title),
        }
       backgroundColor: Colors.white,
);

but if I wanted to get the background color from the controller I would have to:

AppBar get appBar => ControlledWidgetBuilder<Controller>(builder: (context, controller) {
        return AppBar(
                title: Text("Title"),
                backgroundColor: controller.color,
        );
});

and this is invalid because AppBar requires a PreferredSizeWidget

[Question] About initViewState() migration

Hi,
First of all, Thank you for creating this library!

I was using v4.0.1 and I'm migrating my app codes to latest version.

I used initViewState() like this:

class DetailedDataScreen extends View {
  final String uuid;

  DetailedDataScreen(this.uuid, {Key? key});

  @override
  _DetailDataScreenState createState() =>
      _DetailDataScreenState(DetailedDataController(DataServerRepository()));
}

class _DetailDataScreenState
    extends ViewState<DetailedDataScreen, DetailedDataController> {
  _DetailDataScreenState(controller) : super(controller);

  @override
  void initViewState(DetailedDataController controller) {
    super.initState();
    controller.getDataFromServer(uuid: widget.uuid);
  }

  @override
  Widget get view => ...   // draw view about controller.someData with ControlledWidgetBuilder<>
}

but after v4.0.4, View can't access its controller on InitState() because initViewState() was removed.
so if I want to pass some datas (like navigation params) to controller on init timing and execute usecase using this datas, should i use constructor of controller?

like this :

class DetailedDataController extends Controller {
  DetailedDataController(this._serverRepository, this._uuid);

  final ServerRepository _serverRepository;
  final String _uuid;

  SomeData? someData;

  @override
  void onInitState() {
    super.onInitState();
    getSomeDataFromServer(uuid: _uuid);
  }

  @override
  void initListeners() {}

  void getSomeDataFromServer({required String uuid}) => ...
}

it is unclear for me what is right way to migration.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.