Примеры использования Square Flow + Mortar

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

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

Solutions Collecting From Web of "Примеры использования Square Flow + Mortar"

Мы по-прежнему работаем над каноническим ответом на это, но основная идея заключается в том, что вы позволяете ресурсной системе изменять, какие взгляды вы показываете в какой ситуации. Таким образом, ваша активность задает представление содержимого, скажем, R.layout.root_view . Таблетная версия этого макета (мы помещаем его в res/layout-sw600dp ) может быть привязана к различным представлениям, которые могут вводить разные презентаторы и т. Д.

Для случаев, когда вам нужно принять решение во время выполнения, определите логический ресурс в values/bools .xml

 <?xml version="1.0" encoding="utf-8"?> <resources> <bool name="show_tablet_ui">false</bool> </resources> 

И values-sw600dp/bools.xml

 <?xml version="1.0" encoding="utf-8"?> <resources> <bool name="show_tablet_ui">true</bool> </resources> 

Распределите его на остальную часть приложения с помощью кинжала. Используйте эту аннотацию привязки:

 /** * Whether we should show a tablet UI. */ @Retention(RUNTIME) @Qualifier public @interface ShowTabletUi { int ID = R.bool.show_tablet_ui; } 

И метод поставщика, например:

 /** * Singleton because there's no reason to read it from resources again, * it won't change. */ @Provides @ShowTabletUi @Singleton boolean showTabletUi(Resources resources) { return resources.getBoolean(ShowTabletUi.ID); } 

Но подождите, что еще! Предположим, вы хотите иметь одно определение экрана / плана, которое производит разные модули для разных форм-факторов. Мы начали использовать схему аннотаций для упрощения такого рода вещей. Вместо того чтобы наши классы экрана применяли BluePrint , мы начали использовать некоторые аннотации, чтобы объявить их класс интерфейса. В этом мире вот как экран может выборочно выбирать, какие модули использовать для планшетов или мобильных устройств.

 @Layout(R.layout.some_view) @WithModuleFactory(SomeScreen.ModuleFactory.class) public class SomeScreen { public static class ModuleFactory extends ResponsiveModuleFactory<HomeScreen> { @Override protected Object createTabletModule(HomeScreen screen) { return new TabletModule(); } @Override protected Object createMobileModule(HomeScreen screen) { return new MobileModule(); } } 

Магия, не так ли? Вот что стоит за занавеской. Во-первых, ModuleFactory – это некоторый статический класс, которому предоставляется доступ к экрану и ресурсам, и вырывает модуль кинжала.

 public abstract class ModuleFactory<T> { final Blueprint createBlueprint(final Resources resources, final MortarScreen screen) { return new Blueprint() { @Override public String getMortarScopeName() { return screen.getName(); } @Override public Object getDaggerModule() { return ModuleFactory.this.createDaggerModule(resources, (T) screen); } }; } protected abstract Object createDaggerModule(Resources resources, T screen); } 

Наш подкласс ResponsiveModuleFactory trixie выглядит следующим образом. (Помните, как ShowTabletUi.java определил идентификатор ресурса как константу? Вот почему.)

 public abstract class ResponsiveModuleFactory<T> extends ModuleFactory<T> { @Override protected final Object createDaggerModule(Resources resources, T screen) { boolean showTabletUi = resources.getBoolean(ShowTabletUi.ID); return showTabletUi ? createTabletModule(screen) : createMobileModule(screen); } protected abstract Object createTabletModule(T screen); protected abstract Object createMobileModule(T screen); } 

Чтобы сделать все это, у нас есть класс ScreenScoper (см. Ниже). В примере кода Mortar вы должны сделать ScreenConductor одним из них, чтобы создавать и уничтожать области. Рано или поздно (скоро я надеюсь) Mortar и / или его образцы будут обновлены, чтобы включить этот материал.

 package mortar; import android.content.Context; import android.content.res.Resources; import com.squareup.util.Objects; import dagger.Module; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.LinkedHashMap; import java.util.Map; import static java.lang.String.format; /** * Creates {@link MortarScope}s for screens that may be annotated with {@link WithModuleFactory}, * {@link WithModule} or {@link Module}. */ public class ScreenScoper { private static final ModuleFactory NO_FACTORY = new ModuleFactory() { @Override protected Object createDaggerModule(Resources resources, Object screen) { throw new UnsupportedOperationException(); } }; private final Map<Class, ModuleFactory> moduleFactoryCache = new LinkedHashMap<>(); public MortarScope getScreenScope(Context context, final MortarScreen screen) { MortarScope parentScope = Mortar.getScope(context); return getScreenScope(context.getResources(), parentScope, screen); } /** * Finds or creates the scope for the given screen, honoring its optoinal {@link * WithModuleFactory} or {@link WithModule} annotation. Note the scopes are also created * for unannotated screens. */ public MortarScope getScreenScope(Resources resources, MortarScope parentScope, final MortarScreen screen) { ModuleFactory moduleFactory = getModuleFactory(screen); MortarScope childScope; if (moduleFactory != NO_FACTORY) { Blueprint blueprint = moduleFactory.createBlueprint(resources, screen); childScope = parentScope.requireChild(blueprint); } else { // We need every screen to have a scope, so that anything it injects is scoped. We need // this even if the screen doesn't declare a module, because Dagger allows injection of // objects that are annotated even if they don't appear in a module. Blueprint blueprint = new Blueprint() { @Override public String getMortarScopeName() { return screen.getName(); } @Override public Object getDaggerModule() { return null; } }; childScope = parentScope.requireChild(blueprint); } return childScope; } private ModuleFactory getModuleFactory(MortarScreen screen) { Class<?> screenType = Objects.getClass(screen); ModuleFactory moduleFactory = moduleFactoryCache.get(screenType); if (moduleFactory != null) return moduleFactory; WithModule withModule = screenType.getAnnotation(WithModule.class); if (withModule != null) { Class<?> moduleClass = withModule.value(); Constructor<?>[] constructors = moduleClass.getDeclaredConstructors(); if (constructors.length != 1) { throw new IllegalArgumentException( format("Module %s for screen %s should have exactly one public constructor", moduleClass.getName(), screen.getName())); } Constructor constructor = constructors[0]; Class[] parameters = constructor.getParameterTypes(); if (parameters.length > 1) { throw new IllegalArgumentException( format("Module %s for screen %s should have 0 or 1 parameter", moduleClass.getName(), screen.getName())); } Class screenParameter; if (parameters.length == 1) { screenParameter = parameters[0]; if (!screenParameter.isInstance(screen)) { throw new IllegalArgumentException(format("Module %s for screen %s should have a " + "constructor parameter that is a super class of %s", moduleClass.getName(), screen.getName(), screen.getClass().getName())); } } else { screenParameter = null; } try { if (screenParameter == null) { moduleFactory = new NoArgsFactory(constructor); } else { moduleFactory = new SingleArgFactory(constructor); } } catch (Exception e) { throw new RuntimeException( format("Failed to instantiate module %s for screen %s", moduleClass.getName(), screen.getName()), e); } } if (moduleFactory == null) { WithModuleFactory withModuleFactory = screenType.getAnnotation(WithModuleFactory.class); if (withModuleFactory != null) { Class<? extends ModuleFactory> mfClass = withModuleFactory.value(); try { moduleFactory = mfClass.newInstance(); } catch (Exception e) { throw new RuntimeException(format("Failed to instantiate module factory %s for screen %s", withModuleFactory.value().getName(), screen.getName()), e); } } } if (moduleFactory == null) moduleFactory = NO_FACTORY; moduleFactoryCache.put(screenType, moduleFactory); return moduleFactory; } private static class NoArgsFactory extends ModuleFactory<Object> { final Constructor moduleConstructor; private NoArgsFactory(Constructor moduleConstructor) { this.moduleConstructor = moduleConstructor; } @Override protected Object createDaggerModule(Resources resources, Object ignored) { try { return moduleConstructor.newInstance(); } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { throw new RuntimeException(e); } } } private static class SingleArgFactory extends ModuleFactory { final Constructor moduleConstructor; public SingleArgFactory(Constructor moduleConstructor) { this.moduleConstructor = moduleConstructor; } @Override protected Object createDaggerModule(Resources resources, Object screen) { try { return moduleConstructor.newInstance(screen); } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { throw new RuntimeException(e); } } } }