A real-time chat application to showcase dart_pusher_channels.
Visit test.pusher.com, ensure that the cluster set to mt1
and try to trigger some messages. In the app's chat room you should be able to see the messages triggered from the website.
- FDD (Feature-Driven-Design) is used as a primary architectural solution.
- MVP (Model-View-Presenter) pattern is used for business logic interactions on the presentation layer. Blocs are associated with
AppModel
interface implementations. - get_it + injectable are used for dependency injection.
- mockito framework is used for tests.
- dart_pusher_channels is primarily used in the data layer for real-time communications.
core
- contains structures that are used as basis for a whole application.di
- contains the config file generated byinjectable
.localization
- contains helpful structures and utils to work with localization.navigation
- containsAppNavigator
class and the mixins for routes of general use.
[feature_name]/data
- contains structures used for the data layer, such as the client factories, constants or the repository implementations. They are supposed to fulfill all contracts of the domain layer.[feature_name]/data/repositories
- provides implementations for the repository contracts of the domain layer.[feature_name]/data/constants
- contains constant classes with specific static fields, such as URLs and strings.
[feature_name]/domain/repositories
- major domain contracts (repository interfaces) that are accessed upon calling the use-case classes in the presentation layer.[feature_name]/domain/entities
- static models (real-world entities) that must be returned by the domain contracts.[feature_name]/domain/usecases
- delegates that provide some specific functionality upon using the repository.[feature_name]/domain/stores
- stores that are used to manage some global values. Typically implemented usingBloc
s.
[feature_name]/presentation/blocs
- containsBloc
s that are used as parts of instances ofAppModel
class inMVP
ecosystem.[feature_name]/presentation/widgets
- contains the widgets of general use.[feature_name]/presentation/pages
- contains the page directories where each should contain specific files required per one page.[feature_name]/presentation/[feature_name]_navigator.dart
- contains an implementer class which fulfills
PerPageNavigatorMixin
and other dependent route-mixins. - provides the route-mixins of pages and dialogs which are associated with the feature.
- contains an implementer class which fulfills
[feature_name]/presentation/pages/[page_name]
- root directory of a specific page.[feature_name]/presentation/pages/[page_name]/[page_name]_model.dart
- class that implementsAppModel
interface. Typically, the model is owned by the presenter. Consists of zero or many instances ofBloc
.[feature_name]/presentation/pages/[page_name]/[page_name]_view.dart
- contains a contract (interface) that provides methods required by the presenter. For example, showing the dialog messages, scrolling to bottom of the list and e.t.c. Associated with some specific presenter. ExtendsAppView
interface.[feature_name]/presentation/pages/[page_name]/[page_name]_presenter.dart
- class that implementsAppPresenter
interface. Owns some specific model and typically does following operations:- Provides public read-only (upcasted to
BlocBase<T>
) access to the blocs of the model that will be listened by the widget class. - Provides public methods to interact with the encapsulated model's blocs.
- Interacts the blocs of the models by a single bloc listener.
- Uses an implementer of the view contract to react on some events delivered from the model's blocs.
- Provides public read-only (upcasted to
[feature_name/presentation/pages/[page_name].dart]
- the stateful widget, the state class of which implements the corresponding view contract and gets bound to the corresponding presenter.
- managing interactions of owned
AppModel
's blocs internally. (Building and providing handlers to a MultiBlocListener) - providing public read-only access to owned
AppModel
's blocs. (Usually, upcasting them to BlocBase). - providing public methods to trigger corresponding operations of owned
AppModel
's blocs.
- providing instances of Bloc for a use by a corresponding presenter.
- providing public methods to interact with UI: showing dialogs, scrolling, navigating to other page and e.t.c.
Typically, a widget state class fulfills the contracts of page-specific interface extending AppView
.
Also, it creates an instance of a presenter respectively to the implemented view interface and binds itself to a presenter providing its contracts. So, a widget state owns an instance of a presenter and provides contracts of a view for the presenter at the same time.
Example:
class _ChatPageState extends State<ChatPage> implements ChatPageView {
// ...
// Binds itself as an implementer of ChatPageView's contracts
// So _presenter does not know that it's _ChatPageState, it recognizes "this" as an instance of ChatPageView
late final ChatPagePresenter _presenter = ChatPagePresenter.fromEnvironment()
..bindView(this);
//...
// Provides an implementation to contracts of ChatPageView
@override
void scrollToBottom() => _scrollToBottom();
// ...
// "Activates" _presenter by wrapping its widget subtree like:
@Override
Widget build(){
return _presenter.buildMultiBlocListener(
context,
BlocBuilder<BlocBase<PusherChannelsConnectionState>,
PusherChannelsConnectionState>(
// listens to changes on upcasted read-only BlocBase instances
bloc: _presenter.readPusherChannelsConnectionCubit,
builder: (context, chatListState) => Text('Hello'),
)
);
}
// ...
// Calls _presenter's operations (For example, to trigger a message)
void _triggerMessage(String message) => _presenter.triggerMessage(message);
// ...
// disposing _presenter when there is no need
@override
void dispose() {
_presenter.dispose();
super.dispose();
}
// ...
}