使用Flow + Mortar + Dagger2保存/恢复屏幕状态的首选方法是什么?

时间:2015-06-24 08:25:12

标签: android flow dagger-2 mortar

我正在尝试将Acticity + Fragments应用转换为Flow + Mortar + Dagger2

我想保存&从屏幕跳到屏幕(至少向后)时恢复屏幕状态。推荐/推荐的方法是什么?

我花了很多时间查看流量和砂浆读数和样本,但无法弄清楚(文档和样本很少,只处理简单/静态/唯一数据)。

比如说,你有一个类似浏览器的应用程序从页面移动到页面每个页面使用相同的PageView类,相同的PagePresenter类但具有不同的动态内容,具体取决于用户键入的url-string

使用Dagger2(编译类型注释)通过注入保存/恢复状态是相当复杂/困难的,对吧​​? (这需要复杂的父/缓存结构)

我用Google搜索了一下,偶然发现: https://github.com/lukaspili/flow-navigation

然而,这主要是一个实验...如果可能的话,我宁愿我的生产资金制作应用程序的官方/可靠/测试/支持/由广场支持

我也看了看:

1)https://github.com/square/flow/issues/11 但是样本使用Dagger在具有不同视图类别(不是有效答案)的2个屏幕中注入数据

2)https://github.com/square/flow/issues/76(没有回答)

3)Mortar / Flow save view and presenter in backstack

我也看到了这个: 我们现在通过简单地向屏幕对象添加一个可变字段来内部执行此操作:

public void setViewState(SparseArray<Parcelable> viewState) {
this.viewState = viewState;
}

public void restoreHierarchyState(View view) {
view.restoreHierarchyState(viewState);
}

当视图被换出时,我们抓住它的实例状态并将其设置在屏幕对象上(已经在backstack上)。在将其推广到图书馆之前,我们将使用这种模式。

但流动样本和迫击炮样本都不使用此解决方案(他们使用dagger2注入列表......不再是有效的解决方案)

那么,什么是 UP TO DATE 最佳/推荐的方法来恢复/保存迫击炮+流量(+ dagger2)应用中的屏幕状态?

3 个答案:

答案 0 :(得分:14)

首先关于Flow&amp; amp;流动通道

  1. Flow会保存上一个视图的视图状态,并尝试恢复以前保存的新视图的状态。
    并且通过视图状态,我的意思是android视图状态,完全独立于Flow。它不会保存与前一个屏幕关联的Mortar范围 您复制粘贴的代码片段已经在Flow中实现,并完全按照我上面的说法执行。

  2. 使用Flow-path,定义如何从屏幕A转到屏幕B的逻辑,如何设置从A到B的视图转换动画,以及如何设置/销毁A的PathContext包装器和B,在PathContainer类中定义。

  3. PathContext是一个来自Flow-path的类,用于设置与Screen及其View相关联的上下文(它是android.content.Context周围的包装器,与工作方式相同Mortar上下文包装器)。您通常还会拥有PathContextFactory调用的自定义PathContainer,并设置与屏幕和PathContext相关联的Mortar范围。

  4. 流量路径不提供任何&#34;官方&#34; PathContainer的实施。唯一的一个是示例项目中的SimplePathContainer 如果查看SimplePathContainer的源代码,您将看到它会破坏与先前screeen关联的路径上下文。通过破坏其上下文,它还会破坏其Mortar范围以及其中的所有内容,例如包含ViewPresenter实例的Dagger2组件。

  5. 如果要保留上一屏幕的Mortar范围,执行此操作的唯一方法是编写自己的PathContainer实现,该实现不会破坏历史记录中的先前范围。 这就是Flow-navigation(https://github.com/lukaspili/flow-navigation)。

  6. 的基本功能
  7. StateParceler用于在Bundle中保存/恢复流历史堆栈。正如你所说,其目的是使历史在配置更改和应用程序进程中消失 但是,通过适当的Mortar配置,Mortar范围在配置更改期间不会被销毁,因此,您不需要保存/恢复ViewPresenter实例,因为这些实例不会被销毁(仅限视图)。你仍然需要为进程杀死做这件事。

  8. 现在我的2cents:

    流量导航是第一次证明不会破坏历史上前一屏幕的迫击炮范围(背书架)的概念。
    从那时起,我从头开始编写了一个备用Flow库,它处理导航,管理Mortar范围的历史记录,并以适合我的需求的分离方式提供视图转换:https://github.com/lukaspili/Mortar-architect

    因为您正在寻找Square支持和支持的解决方案,所以这对您不利。但是,我邀请您查看源代码,这可能会让您了解如何编写自己的PathContainer来保留Flow历史记录中的Mortar范围。

答案 1 :(得分:2)

经过实验,我在Path对象中添加了一些可变字段。

按照设计,迫击炮/ Flow应用程序使用StateParceler将这些Path对象序列化/反序列化为Bundles和Parcel,以便保存和恢复视图状态

通过让StateParceler处理这些可变字段,它们能够在方向更改和反向导航中存活下来。

通过使StateParceler能够将这些可变字段序列化/反序列化为Persistant存储(比如JSon和SharedPreferences),完整的历史记录可以在断电/不同的应用会话中存活

square中的样本使用GsonParceler,它能够为大多数对象提供开箱即用的功能。 您只需要编写一些代码,以便能够使用Generics / interface处理集合和复杂对象......

答案 2 :(得分:0)

基于@lukas的回答和他的图书馆Figure fig = new Figure(); fig.setLayoutManager(new FreeformLayout()); FlowPage flow = new FlowPage(); flow.setSize(100,100); TextFlow text = new textFlow("A rather long text without any line-breaks. Lorem Ipsum dolor sit amet."); flow.add(text); fig.add(flow); fig.setSite(100,100); ,我意识到破坏flow-navigation的违规电话是这一行:

MortarScope

所以我用

替换了它
oldPath.destroyNotIn(context, contextFactory);

public static PathContext create(PathContext previousContext, Path newPath, PathContextFactory factory) { if(newPath == Path.ROOT) { throw new IllegalArgumentException("Path is empty."); } List<Path> newPathElements = newPath.elements(); Map<Path, Context> newContextChain = new LinkedHashMap<>(); // We walk down the elements, reusing existing contexts for the elements we encounter. As soon // as we encounter an element that doesn't already have a context, we stop. // Note: we will always have at least one shared element, the root. Context baseContext = null; Iterator<Path> pathIterator = newPathElements.iterator(); Iterator<Path> basePathIterator = previousContext.path.elements().iterator(); Log.d("PathContext", ":: Creating Context to [" + ((BasePath) newPath).getScopeName() + "]"); while(pathIterator.hasNext() && basePathIterator.hasNext()) { Path element = pathIterator.next(); Path basePathElement = basePathIterator.next(); if(basePathElement.equals(element)) { if(!element.isRoot()) { Log.d("PathContext", "Matched new Path to old Path [" + ((BasePath) element).getScopeName() + "], preserving context."); } else { Log.d("PathContext", "Matched new Path to old Path [ROOT], preserving context."); } baseContext = previousContext.contexts.get(element); newContextChain.put(element, baseContext); } else { if(!basePathElement.isRoot() && !element.isRoot()) { Log.d("PathContext", "No match from [" + ((BasePath) basePathElement).getScopeName() + "] to [" + ((BasePath) element) .getScopeName() + "] , creating new context."); } else { Log.d("PathContext", "No match from ROOT [" + basePathElement + "] to ROOT [" + element + "] , creating new context."); } baseContext = factory.setUpContext(element, baseContext); newContextChain.put(element, baseContext); break; } } // Now we continue walking our new path, creating contexts as we go in case they don't exist. while(pathIterator.hasNext()) { Path element = pathIterator.next(); if(!element.isRoot()) { Log.d("PathContext", "Creating new path [" + ((BasePath) element).getScopeName() + "]."); } else { Log.d("PathContext", "Creating new path [ROOT]."); } baseContext = factory.setUpContext(element, baseContext); newContextChain.put(element, baseContext); } // Finally, we can construct our new PathContext return new PathContext(baseContext, newPath, newContextChain); } /** * Finds the tail of this path which is not in the given path, and destroys it. */ public void destroyNotIn(PathContext path, PathContextFactory factory) { Iterator<Path> aElements = this.path.elements().iterator(); Iterator<Path> bElements = path.path.elements().iterator(); while(aElements.hasNext() && bElements.hasNext()) { Path aElement = aElements.next(); Path bElement = bElements.next(); if(!aElement.equals(bElement)) { BasePath aBasePath = (BasePath) aElement; BasePath bBasePath = (BasePath) bElement; Log.d(toString(), "Destroying [" + aBasePath.getScopeName() + "] on matching with [" + bBasePath.getScopeName() + "]"); factory.tearDownContext(contexts.get(aElement)); break; } } while(aElements.hasNext()) { Path aElement = aElements.next(); BasePath aBasePath = (BasePath) aElement; Log.d(toString(), "Destroying [" + aBasePath.getScopeName() + "] as it is not found in [" + path + "]"); factory.tearDownContext(contexts.get(aElement)); } } 只返回BasePath。它基本上就像Square已删除的getScopeName()

此外,BluePrint摧毁了以前的所有内容,所以我也对其进行了修改。

SimplePathContainer