Архитектура приложения Flutter с использованием «модулей» с пакетом провайдера - PullRequest
0 голосов
/ 25 апреля 2020

Я кодирую приложение во Flutter уже несколько недель и начал задумываться о том, какой может быть лучшая архитектура через несколько дней за go.

Сначала немного контекста:

  • Это приложение для обмена сообщениями, использующее Firebase в качестве бэкэнда;
  • В значительной степени оно опирается на замечательный пакет Provider для обработки состояния во всем приложении
  • План состоит в том, чтобы иметь несколько функций, которые могут взаимодействовать друг с другом.
  • Я довольно новичок в Flutter (в основном фон React / ReactNative), это может объяснить странный подход, который я ниже.

Я испытывал различные подходы к архитектуре и смог получить один рабочий, который, наконец, мне подходит.

Поскольку у меня будет несколько функций, которые можно использовать в разных местах приложения, я хочу разделить код по функциям (или модулям), которые затем могут использоваться независимо на разных экранах. Архитектура папок будет выглядеть следующим образом:

FlutterApp
|
|--> ios/
|--> android/
|--> lib/
      |
      |--> main.dart
      |--> screens/
      |       |
      |       |--> logged/
      |       |      |
      |       |      |--> profile.dart
      |       |      |--> settings.dart
      |       |      |--> ...
      |       |
      |       |--> notLogged/
      |       |      |
      |       |      |--> home.dart
      |       |      |--> loading.dart
      |       |      |--> ...
      |       
      |--> features/
              |
              |--> featureA/
              |       |
              |       |--> ui/
              |       |     |--> simpleUI.dart
              |       |     |--> complexUI.dart
              |       |--> provider/
              |       |     |-->featureAProvider.dart
              |       |--> models/
              |             |--> featureAModel1.dart
              |             |--> featureAModel2.dart
              |             |--> ...
              |
              |
              |--> featureB/
              |       |
              |       |--> ui/
              |       |     |--> simpleUI.dart
              |       |     |--> complexUI.dart
              |       |--> provider/
              |       |     |--> featureBProvider.dart
              |       |--> models/
              |             |--> featureBModel1.dart
              |             |--> featureBModel2.dart
              |             |--> ...
              |
             ...

В идеале каждая функция будет следовать следующим правилам:

  • Каждая функция имеет часть logi c (часто с использованием пакета поставщика);
  • Каждая часть logi c компонента может запрашивать переменные (члены класса ChangeNotifier) ​​у другого Feature
  • Каждая функция имеет (тупую) часть пользовательского интерфейса, которая может напрямую взаимодействовать с "logi c "part (таким образом, может быть, не так глупо);
  • Каждая функция может иметь свою часть пользовательского интерфейса, заменяемую пользовательским интерфейсом, но затем пользовательский интерфейс должен реализовывать взаимодействие (я) с частью logi c на свой собственный;
  • Каждая функция может иметь модели, представляющие ресурсы ресурсов, если мне потребуется сохранить их в Firebase позже

Я пробовал этот подход с одной функцией (или 2 зависит от того, как вы это видите) моего приложения, которое представляет собой возможность записи / прослушивания голосовых заметок. Мне это показалось интересным, потому что вы можете записывать в одном месте, но прослушивать запись во многих местах: например, сразу после записи или когда вам отправляют запись.

Вот что я придумал :

  • структура папок теста В этом случае нет папки models/, потому что это просто файл, который я обрабатываю в другом месте
  • voiceRecorderNotifier обрабатывает файл (добавить / удалить) и запись (начало / конец)
  • VoicePlayerNotifier требует, чтобы файл был создан (именованный конструктор), а затем обрабатывает воспроизведение аудиофайла (воспроизведение, пауза, стоп).

В коде это немного многословно, но работает, как и ожидалось, например, на экранах я могу запросить функцию voiceRecorder, например:

class Screen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (_) => VoiceRecorderNotifier(),
      child: Column(
        children: [
          AnUIWidget(),
          AnotherUIWidget(),
          ...,
          // The "dumb" feature UI widget from 'features/voiceRecorder/ui/simpleButton.dart' that can be overrided if you follow use the VoiceRecorderNotifier
          VoiceRecorderButtonSimple(), 
          ...,
        ]
      )
    );
  }
}

У меня также могут быть две функции (voiceRecorder / voicePlayer), работающие вместе, например:

class Screen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (_) => VoiceRecorderNotifier(),
      child: Column(
        children: [
          AnUIWidget(),
          AnotherUIWidget(),
          ...,
          VoiceRecorderButtonSimple(),
          ...,
          // adding the dependent voicePlayer feature (using the VoiceRecorderNotifier data);
          Consumer<VoiceRecorderNotifier>(
            builder: (_, _voiceRecorderNotifier, __) {
              if (_voiceRecorderNotifier.audioFile != null) {
                // We do have audio file, so we put the VoicePlayer feature
                return ChangeNotifierProvider<VoicePlayerNotifier>(
                  create: (_) => VoicePlayerNotifier.withFile(_voiceRecorderNotifier.audioFile),
                  child: VoicePlayerButtonSimple(),
                );
              } else {
                // We don't have audio file, so no voicePlayer needed
                return AnotherUIWidget(); 
              }
            }
          ),
          ...,
          AnotherUIWidget(),
        ]
      )
    );
  }
}

Это тест fre sh, поэтому я предполагаю, что есть недостатки, которые я не вижу сейчас, но я чувствую, что есть несколько хороших моментов:

  • Более чистая структура папок;
  • Одно место для обработки "высокоуровневых" логи c, связанных с функцией, легко обновление;
  • Легко добавлять, перемещать, удалять функцию везде, где есть приложение;
  • Базовый c пользовательский интерфейс предоставляется для функции, как ожидается, такой как простой
Text('hi')

, но я все еще могу "переопределить" пользовательский интерфейс для конкретного c отображения функции;

  • Я могу сосредоточиться на пользовательском интерфейсе и использовании функций, а не создание множества компонентов Stateful для репликации одного и того же лога c элемента в разных местах;

Недостатки, которые я вижу:

  • Элемент Logi c - это «скрытый», мне нужно будет go через Уведомитель каждый раз, когда я хочу сделать что-то, определяющее c с функцией, чтобы запомнить, как работает функция;
  • Реализация уведомителей в хорошем месте может стать беспорядком, если виджеты пользовательского интерфейса могут иметь несколько функций, т тогда мне потребуется несколько FeatureNotifier (даже если в этом случае полезен Multiprovider);

Наконец, вот вопросы:

  • Считаете ли вы этот подход масштабируемым / рекомендуемым, если я смогу продолжать создавать функции таким образом, не испытывая проблем позже?
  • Видите ли вы другие недостатки?
...