iOS和AND,PDQ的MVP

这是sqldelight和rxjava首次亮相的后续活动。 对于背景,请阅读原始的Doppl帖子。

总之,Doppl是围绕j2objc构建的一组工具和库。 最终结果是一个以Android为中心的跨平台代码共享框架,试图成为构建适用于Android和iOS的本机应用程序的最有效方法。

在以下情况下,这对您应该很有趣:

  1. 您正在构建移动产品,并且已决定构建本机产品
  2. 您有一些不平凡的业务逻辑(数据,离线等)

Android在任何平台上都有一些最好的开源框架。 计划是利用这些功能,并在Android和iOS之间尽可能多地共享非UI逻辑。 用户界面将使用本机工具构建。

关键点:

  1. Android是100%Android。 没有“跨平台”。 Doppl不会影响Android开发时间,也不会带来任何第三平台风险。
  2. iOS是Java的Objective-C。

该概念是在不影响开发效率或风险的情况下共享您的业务逻辑。 您只需减去iOS端的业务逻辑工作量。

得到它了? 好。

今天,我们将讨论有关Doppl的框架添加,但更重要的是它们的应用,以及如何构建应用以及可以实现真正效率的愿景。 更简单地说,技术上的“为什么”。

MVP

最近有很多关于MVP架构(以及MVVM,可能还有其他MV_选项)的讨论。 基本思想是将逻辑及其对视图的协调与实际视图逻辑分开。 不必太罗word,这将有助于可测试性并降低复杂性。

根据设计,J2objc不会直接处理UI。 与标准的Android开发人员相比,它具有开箱即用的原始环境。 一旦包含一组可靠的Android库,就可以对整个MVP堆栈进行建模,并与iOS代码共享所有逻辑。 您可以一次执行“最佳实践”架构。

sqldelight示例已进行了以下扩充,以进行演示。

Dagger已添加用于依赖项注入,这并不是严格必需的,但它在那里。

使用Doppl测试运行程序编写了一些示例测试,该运行程序在Java端委托给Robolectric,并在iOS中提供了一个简单的运行程序,提供了测试Android上下文。

iOS单元测试当前不在命令行上运行。 您可以构建它们并从xcode的应用程序中运行它们。 Doppl测试跑步者的参与度远不及Robolectric。 您仅可以访问上下文进行测试。 该代码需要正常的线程工作。 不过效果很好。

只有几个示例测试,但是您可以看到在一个更强大的应用程序中它将如何工作。 同样,要明确一点, 如果这是一个真实的应用程序,则将进行一些测试 ,但是该演示表明体系结构构建块很容易获得。

从结构上讲,存在演示者类,这些类具有宿主接口,这些接口实现了实际操作UI的调用。 目标是使UI逻辑层尽可能窄,以最大程度地提高代码重用性。

如果您查看应用程序代码,则会在“ com.example.sqldelight.hockey.ui”中实现Android UI。 这主要是用于Activity的代码,用于ListView的行定义以及一个从数据文件夹委托给CursorWrapper的简单适配器。 胶水代码,基本上。 在iOS端,此相同功能通过UIViewController和UITableViewCell实现实现。

然后,您将拥有逻辑上的第3个UI,即您的测试。 我们对演示者级别的代码进行了一些模拟,以检查是否按预期方式进行了调用。 无论如何…

读者注意事项。 这本应该是一篇关于MVP的快速“离我远去”的帖子。 然后,其中一半是关于我在演示中添加的时髦UI界面。 我意识到这一点,但我没有尝试完善该职位,而是不进行发布,而是将其保留了下来。 如果您急于跳到“是的,好的,但是为什么?”。

为了进行比较,这是Swift中的行定义

PlayerTableViewCell :UITableViewCell,SDDPlayersPresenter_Row { 
  // MARK:属性 
@IBOutlet弱var playerName:UILabel!
@IBOutlet弱var teamName:UILabel!
@IBOutlet弱var playerNumber:UILabel!
  func getPlayerName ()-> UIDPTextView { 
返回playerName为! UIDPTextView;
}

func getTeamName ()-> UIDPTextView {
返回teamName为! UIDPTextView;
}

func getPlayerNumber ()-> UIDPTextView {
返回playerNumber为! UIDPTextView;
}
}

上面的代码将UILabel强制转换为我们的共享代码可以调用的接口。 共享的代码将“直接”填充UI。

现在是Java

 公共最终类PlayerRow扩展LinearLayout实现PlayersPresenter.Row 
{
@BindView(R.id.player_name)TextView播放器名称;
@BindView(R.id.team_name)TextView teamName;
@BindView(R.id.player_number)TextView播放器编号;
  DPTextView dpPlayerName; 
DPTextView dpPlayerNumber;
DPTextView dbTeamName;
  public PlayerRow (上下文上下文,AttributeSet属性){ 
超级(上下文,attrs);
}
  @Override受保护的void onFinishInflate (){ 
super.onFinishInflate();
ButterKnife.bind(this);
  dpPlayerName =新的DPTextViewWrapper(playerName); 
dpPlayerNumber =新的DPTextViewWrapper(playerNumber);
dbTeamName =新的DPTextViewWrapper(teamName);
}
  @Override 
公共DPTextView getPlayerName ()
{
返回dpPlayerName;
}
  @Override 
公共DPTextView getPlayerNumber ()
{
返回dpPlayerNumber;
}
  @Override 
公共DPTextView getTeamName ()
{
返回dbTeamName;
}
}

类似。 额外的逻辑使UI膨胀。 注意DPTextView的get方法。 它们的作用与快速代码相同,只是我们实际上必须使用Java创建包装器类。 在这种情况下,Objective-C实际上使事情变得更容易。 我们只是向现有的UILabel类添加了一个接口。

在共享的演示者代码中,这是“行”的定义

 公共接口 
{
DPTextView getPlayerName ();
DPTextView getTeamName ();
DPTextView getPlayerNumber ();
}

以及与“行”实际交互的代码

 公共无效fillRow (玩家玩家,团队, 行行
{
//注意,我们正在根据共享代码设置UI文本,并且本机UI具有在每个平台上实现'setText'的适配器
行。 getPlayerName ().setText(player.first_name()+“” + player.last_name());
  if(row.getTeamName()!= null) 
{
row.getTeamName()。setText(team.name());
row.getPlayerNumber()。setText(String.valueOf(player.number()));
}
其他
{
row.getPlayerNumber()。setText(String.valueOf(player.number())+“-” + team.name());
}
  } 

“ DPTextView”只是一个实验(它也打破了MVP,但请继续尝试)。 Android和iOS的基本UI工具集非常相似,直到您了解更复杂的内容为止。 我已经定义了一个包装器接口树,可用于共享代码以及Java和Objective-C的实现。 如前所述,Objc端实际上更简单,因为我们在UIKit类中添加了“类别”,这使我们可以实现包装器接口并添加必要的方法。

因此,例如,DPTextView定义:

  void setText(String s); 

在Objective-C中,我们具有UILabel + DPTextView.h,其中包含…

  @implementation UILabel(DPTextView) 
  -(void)setTextWithNSString:(NSString *)text { 
[self setText:text];
}
  @结束 

无论如何,“ DP” UI东西是新的,我不确定是否实用,但是您明白了。 使UI尽可能哑而窄。 我认为在大多数情况下,互动会更加粗糙。 您将使用一些数据调用主机,以显示数据并将其路由到本机代码中的UI组件。 但是,您可以将其中一些推送到共享层,如所示。

是的,可以,但是为什么呢?

这里的要点是,您可以使用当前的“最佳实践”来构建Android应用程序,而无需对“跨平台”进行任何特殊考虑,除了可以将类放入哪个程序包之外,并绝对确保将UI位保留在“ ui”中包装。 您在Android方面的开发效率为100%,并且由于本机平台是构建单个平台的最快方法,因此您的Android构建速度尽可能快。 这意味着有效的产品迭代

同样,对于任何跨平台解决方案而言,它都是跨平台解决方案中仅有的一半。 Android方面是100%Android。 尽管我是Android爱好者,但Android开发人员的风险更大。 我们支持更多并发版本。 屏幕尺寸很多。 有很多时髦的设备。 为Android构建的风险最小的方法是使用Android Studio和Java。 “风险”(如果有的话)在iOS方面,其设备和版本情况要相似得多。

因此,如果最佳实践讨论中通常建议将“ ui”和“逻辑”完全分开,则可以在Xcode中运行相同的逻辑。 要生成您的iOS应用,您只需简单地连接UI组件。 对于我可能会遇到的所有麻烦,Xcode绝对是制作iOS前端最简单的地方。

为了了解代码的iOS方面是如何工作的,以下是Swift中团队屏幕UIViewController的代码:

  //团队屏幕控制器。  注意SDDTeamPresenter_Host。  这是团队演示者的回调接口 
TeamViewController :UIViewController,UITableViewDelegate,UITableViewDataSource,SDDTeamPresenter_Host {

var selectedTeam = Int64(SDDPlayersPresenter_NOTHING);
让showTeamPlayersId =“ showTeamPlayers”
  //当用户单击一行时。  这是主机界面的一部分 
公共功能showTeamPlayers (withLong id_:Int64){
// segue
self.selectedTeam = id_
self.performSegue(withIdentifier:showTeamPlayersId,发送者:self)
}
  // presenter实例 
var主持人:SDDTeamPresenter!

@IBOutlet
var tableView:UITableView!

覆盖func viewDidLoad (){
super.viewDidLoad()
  //注入匕首。  注入演示者字段 
主持人= SDDTeamPresenter()
让appDelegate = UIApplication.shared.delegate为! AppDelegate
让presenterComponent = SDDDaggerPresentersComponent_builder()。appModule(with:appDelegate.appModule).build()
presenterComponent?.inject(with:Presenter)
presenter.init __(with:self)


self.tableView.register(UITableViewCell.self,forCellReuseIdentifier:“ cell”)

//加载视图后进行其他任何设置。
}
  // Swift的onDestroy版本(ish)。  iOS上的内存有所不同,因此有时您需要显式关闭内容 
deinit {
presenter.close()
}
  // Android上的UITableViewDataSource和Adapter非常相似。  CursorWrapper是一个非常简单的委托,可提供跨平台相同的功能。 
func tableView (_ tableView:UITableView,numberOfRowsInSection部分:Int)-> Int {
返回Int(presenter。getCursorWrapper().rowCount());
}

func tableView (_ cellForRowAttableView:UITableView,cellForRowAt indexPath:IndexPath)-> UITableViewCell {
让单元格= self.tableView.dequeueReusableCell(withIdentifier:“ TeamTableViewCell”,for:indexPath)为! TeamTableViewCell
让team:SDDTeam =主持人。 getCursorWrapper ().atRow(with:Int32(indexPath.row))as! SDDTeam
  //利用演示者执行任何显示格式逻辑 
presenter.fillRow(with:team,with:cell)

返回单元
}

func tableView (_ tableView:UITableView,didSelectRowAt indexPath:IndexPath){
  //在这个例子中,我们只是转身调用主机。  在一个真实的示例中,这可以直接在Swift中直接处理,但是为了演示如何将所有逻辑保留在演示者中,我们将调用演示者。  演示者拥有数据,因此我们只是在传递职位。 
presenter.rowClicked(with:Int32(indexPath.row))
}
  // MARK:-导航 
覆盖功能准备 (对于segue:UIStoryboardSegue,发件人:任何?){
如果segue.identifier == showTeamPlayersId
{
如果让destinationVC = segue.destination为? PlayersViewController {
  //告诉PlayersViewController要显示哪个团队 
destinationVC.teamId = self.selectedTeam
}
}
}
  } 

那里有一些冗长的代码,但没有真正的“逻辑”。 其所有胶水代码。 重要的是它的“哑巴”。 有了功能完善的Android应用程序和设计,您应该能够快速且可预测地构建iOS端。

对于相当标准的应用程序,此框架的生产版本将提供最有效/风险最小的方法来在Android和iOS上构建本机体验。

测试中

当前,测试需要非常接近逻辑和基本线程。 多普勒还处于早期阶段。 就是这样。 您可以在Android端使用Robolectric,但是提供测试上下文之外的任何操作都无法在iOS上运行。 不过,这主要是UI方面的内容,因此处理量不大。

正常的Junit测试通常运行正常。 要运行Robolectric,请添加一个特殊的运行器

  @RunWith(DoppelRobolectricTestRunner.class) 
公共课程HomePresenterTest

获取上下文

  DopplRuntimeEnvironment.getApplication() 

您在应用程序中运行iOS测试。 Xcode中有一个单独的测试目标。

其他的东西

小事。 共享代码使用Retrolambda,并且该代码在两个平台上都能正常工作。

RxJava本身的测试通过> 99%,其余的都有明确的修复程序。 主要围绕大内存发布堆栈。 但是,j2objc不会被垃圾收集,并且有一些重要的,不重要的工作可以将其100%地分类。 示例应用程序中使用的部件不会泄漏内存,但有些会泄漏。 仅供参考。

运行代码

我从整个项目中删除了sqldelight示例,以简化处理过程。

doppllib / sqldelight-sample
通过在GitHub上创建一个帐户为sqldelight-sample开发做出贡献。 github.com

如前所述,您无需运行Java转换。 您可以简单地运行iOS版本。 我不知道我们是否会在生产版本中提交生成的iOS代码。 这会在您的提交中产生很多噪音。 但是,这只是一个示例,您想戳一下。 修复配置不好玩,因此我尝试将其最小化。

所以,向前。 开始与一些试点合作伙伴合作。 如果您想获得更多信息,请联系。