这是sqldelight和rxjava首次亮相的后续活动。 对于背景,请阅读原始的Doppl帖子。
总之,Doppl是围绕j2objc构建的一组工具和库。 最终结果是一个以Android为中心的跨平台代码共享框架,试图成为构建适用于Android和iOS的本机应用程序的最有效方法。
在以下情况下,这对您应该很有趣:
- 您正在构建移动产品,并且已决定构建本机产品
- 您有一些不平凡的业务逻辑(数据,离线等)
Android在任何平台上都有一些最好的开源框架。 计划是利用这些功能,并在Android和iOS之间尽可能多地共享非UI逻辑。 用户界面将使用本机工具构建。
- 在Flutter表单上进行异步验证的秘诀
- 开发Web和移动应用程序时要考虑的6个方面
- 使用一些出色的Android Studio插件可以更好地编码
- 软件开发:近岸革命
- 定制应用程序开发为您的企业带来的五个好处
关键点:
- Android是100%Android。 没有“跨平台”。 Doppl不会影响Android开发时间,也不会带来任何第三平台风险。
- 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代码。 这会在您的提交中产生很多噪音。 但是,这只是一个示例,您想戳一下。 修复配置不好玩,因此我尝试将其最小化。
所以,向前。 开始与一些试点合作伙伴合作。 如果您想获得更多信息,请联系。