【问题标题】:Square Flow + Mortar tablet examplesSquare Flow + Mortar 平板电脑示例
【发布时间】:2014-03-24 17:37:43
【问题描述】:

我一直在尝试使用 Flow 和 Mortor 作为我们 Android 应用的替代架构。我一直在开发一个应用程序,该应用程序目前只有一个手机布局,但我想知道如果你想为平板电脑设置不同的布局,流和砂浆架构如何工作。主细节可能是最简单的例子,但显然还有其他例子。

我有一些想法,但我想知道广场开发人员可能已经围绕这个主题想到了什么。

【问题讨论】:

    标签: android square square-flow mortar


    【解决方案1】:

    我们仍在为此制定规范的答案,但基本思想是让资源系统更改您在哪种情况下显示的视图。因此,您的活动将其内容视图设置为 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>
    

    通过 dagger 将其暴露给应用程序的其余部分。使用这个绑定注解:

    /**
     * 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);
    }
    

    我们的 trixie ResponsiveModuleFactory 子类看起来像这样。 (还记得ShowTabletUi.java 是如何将资源 id 定义为常量的吗?这就是原因。)

    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 使用其中之一来创建和销毁作用域。迟早(我希望很快)迫击炮和/或其样本将被更新以包含这些东西。

    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);
          }
        }
      }
    }
    

    【讨论】:

    • 我昨天第一次发布的 ScreenScoper 有一个缓存错误。我刚刚对其进行了修复。
    • 这非常有用!什么是 MortarScreen 类?它是所有 Screens 扩展的空类吗?与蓝图有何不同?
    • @NelsonOsacky 是一个定义了String getName()方法的接口。
    猜你喜欢
    • 2018-07-30
    • 2016-10-25
    • 1970-01-01
    • 2011-12-14
    • 1970-01-01
    • 1970-01-01
    • 2014-06-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多