Project Name | Stars | Downloads | Repos Using This | Packages Using This | Most Recent Commit | Total Releases | Latest Release | Open Issues | License | Language |
---|---|---|---|---|---|---|---|---|---|---|
Ribs | 7,388 | 6 | 11 days ago | 12 | April 24, 2022 | 111 | apache-2.0 | Kotlin | ||
Uber's cross-platform mobile architecture framework. | ||||||||||
Effectiveandroidui | 6,058 | 2 years ago | 5 | apache-2.0 | Java | |||||
Sample project created to show some of the best Android practices to work in the Android UI Layer. The UI layer of this project has been implemented using MVP or MVVM (without binding engine) to show how this patterns works. This project is used during the talk "EffectiveAndroidUI". | ||||||||||
Androidproject | 5,116 | 9 months ago | 1 | apache-2.0 | Java | |||||
Android 技术中台,但愿人长久,搬砖不再有 | ||||||||||
Westore | 4,123 | 1 | 19 days ago | 7 | September 27, 2021 | 61 | JavaScript | |||
小程序项目分层架构 | ||||||||||
Archi | 3,363 | 4 years ago | 11 | apache-2.0 | Java | |||||
Repository that showcases 3 Android app architectures: "Standard Android", MVP and MVVM. The exact same app is built 3 times following the different patterns. | ||||||||||
Mvvm Kotlin Android Architecture | 2,053 | 5 months ago | 6 | apache-2.0 | Kotlin | |||||
MVVM + Kotlin + Retrofit2 + Hilt + Coroutines + Kotlin Flow + mockK + Espresso + Junit5 | ||||||||||
Android Learning Resources | 1,289 | 4 years ago | 2 | |||||||
Android学习资源网站索引大全 | ||||||||||
Devutils | 1,114 | 8 months ago | 16 | July 04, 2022 | 1 | apache-2.0 | Java | |||
:fire: ( 持续更新,目前含 300+ 工具类 ) DevUtils 是一个 Android 工具库,主要根据不同功能模块,封装快捷使用的工具类及 API 方法调用。该项目尽可能的便于开发人员,快捷、高效开发安全可靠的项目。 | ||||||||||
Thirtyinch | 1,041 | 2 years ago | 34 | apache-2.0 | Java | |||||
a MVP library for Android favoring a stateful Presenter | ||||||||||
Bigshow1949 | 1,036 | 2 years ago | 7 | Objective-C | ||||||
iOS教学/各类知识点总结:运行时/贝塞尔曲线/水纹/粒子发射器/核心动画/渐变色/网络请求/按钮/标签/视图布局/视图效果/文字视图/表情键盘/旋转动画/2048/网易/微信/猿题库/阿里巴巴/设计模式/数据持久化/多次点击按钮/微信注册按钮/展开按钮/跑马灯/闪烁文字/球形滚动标签/自动布局标签/快播动态标签/水平滚动布局/瀑布流布局/浏览卡/半圆布局/滑动标题/抽卡效果/百度视图切换/领英动画/折卡效果/卡牌拖动翻页/滚动悬浮视图/侧滑形变效果/评分条/打印机特效/Masonry/生命周期/响应者链条/引导页/通知中心/抖动密码框/余额宝数字跳动/UIDynamic/碰撞行为/捕捉行为/推动行为/附着行为/动力元素行为/GCD/KVC&KVO/多继承/消息转发/二维码/MVC/MVP/MVVM/Router/Viper |
This library adds Presenters to Activities and Fragments. It favors the stateful Presenter pattern, where the Presenter survives configuration changes and dumb View pattern, where the View only sends user events and receives information from the Presenter but never actively asks for data. This makes testing very easy because no logic lives in the View (Activity, Fragment) except for fancy animations which anyways aren't testable.
Keep Android At Arms Length
— Kevin Schultz, Droidcon NYC '14
The perfect distance to the Android Framework is approximately thirty inches, the average length of the human arm, shoulder to fingertips.
Read the introduction article on Medium
See the slides of the latest talk on Speakerdeck
repositories {
maven {
url = uri("https://maven.pkg.github.com/GCX-HCI/ThirtyInch")
}
}
dependencies {
implementation "net.grandcentrix.thirtyinch:thirtyinch:$thirtyinchVersion"
implementation "net.grandcentrix.thirtyinch:thirtyinch-rx2:$thirtyinchVersion"
implementation "net.grandcentrix.thirtyinch:thirtyinch-logginginterceptor:$thirtyinchVersion"
implementation "net.grandcentrix.thirtyinch:thirtyinch-kotlin:$thirtyinchVersion"
implementation "net.grandcentrix.thirtyinch:thirtyinch-kotlin-coroutines:$thirtyinchVersion"
// Legacy dependencies
implementation "net.grandcentrix.thirtyinch:thirtyinch-rx:$thirtyinchVersion"
}
repositories {
jcenter()
}
dependencies {
implementation "net.grandcentrix.thirtyinch:thirtyinch:$thirtyinchVersion"
implementation "net.grandcentrix.thirtyinch:thirtyinch-rx2:$thirtyinchVersion"
implementation "net.grandcentrix.thirtyinch:thirtyinch-logginginterceptor:$thirtyinchVersion"
implementation "net.grandcentrix.thirtyinch:thirtyinch-kotlin:$thirtyinchVersion"
implementation "net.grandcentrix.thirtyinch:thirtyinch-kotlin-coroutines:$thirtyinchVersion"
// Lagacy dependencies
implementation "net.grandcentrix.thirtyinch:thirtyinch-rx:$thirtyinchVersion"
}
HelloWorldActivity.java
public class HelloWorldActivity
extends TiActivity<HelloWorldPresenter, HelloWorldView>
implements HelloWorldView {
private TextView mOutput;
@NonNull
@Override
public HelloWorldPresenter providePresenter() {
return new HelloWorldPresenter();
}
@Override
public void showText(final String text) {
mOutput.setText(text);
}
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_hello_world);
mOutput = (TextView) findViewById(R.id.output);
}
}
HelloWorldView.java
public interface HelloWorldView extends TiView {
@CallOnMainThread
void showText(final String text);
}
HelloWorldPresenter.java
public class HelloWorldPresenter extends TiPresenter<HelloWorldView> {
@Override
protected void onAttachView(@NonNull final HelloWorldView view) {
super.onAttachView(view);
view.showText("Hello World!");
}
}
Activity
got killed in backgroundActivity
gets finishedThe TiPresenter
lifecycle is very easy.
It can be CREATED
and DESTROYED
.
The corresponding callbacks onCreate()
and onDestroy()
will be only called once!
The TiView
can either be ATTACHED
or DETACHED
.
The corresponding callbacks are onAttachView(TiView)
and onDetachView()
which maps to onStart()
and onStop()
.
public class MyPresenter extends TiPresenter<MyView> {
@Override
protected void onCreate() {
super.onCreate();
}
@Override
protected void onAttachView(@NonNull final HelloWorldView view) {
super.onAttachView(view);
}
@Override
protected void onDetachView() {
super.onDetachView();
}
@Override
protected void onDestroy() {
super.onDestroy();
}
}
The lifecycle can be observed using TiLifecycleObserver
There is no callback for onResume()
and onPause()
in the TiPresenter
.
This is something the view layer should handle.
Read more about this here Hannes Dorfmann - Presenters don't need lifecycle events
The default behaviour might not fit your needs.
You can disable unwanted features by providing a configuration in the TiPresenter
constructor.
public class HelloWorldPresenter extends TiPresenter<HelloWorldView> {
public static final TiConfiguration PRESENTER_CONFIG =
new TiConfiguration.Builder()
.setRetainPresenterEnabled(true)
.setCallOnMainThreadInterceptorEnabled(true)
.setDistinctUntilChangedInterceptorEnabled(true)
.build();
public HelloWorldPresenter() {
super(PRESENTER_CONFIG);
}
}
Or globally for all TiPresenters
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
TiPresenter.setDefaultConfig(MY_DEFAULT_CONFIG);
}
}
Two awesome annotations for the TiView
interface made it already into Ti
saving you a lot of time.
public interface HelloWorldView extends TiView {
@CallOnMainThread
@DistinctUntilChanged
void showText(final String text);
}
Whenever you call this method it will be called on the Android main thread. This allows to run code off the main thread but send events to the UI without dealing with Handlers and Loopers.
Requires to be a void
method. Works only for TiView
interfaces implemented by "Android Views" (TiActivity
, TiFragment
).
Enabled by default, can be disabled with the TiConfiguration
When calling this method the View
receives no duplicated calls.
The View swallows the second call when a method gets called with the same (hashcode) parameters twice.
Usecase:
The Presenter binds a huge list to the View
. The app loses focus (onDetachView()
) and the exact same Activity instance gains focus again (onAttachView(view)
).
The Activity
still shows the huge list.
The Presenter
binds the huge list again to the View
.
When the data has changed the list will be updated.
When the data hasn't changed the call gets swallowed and prevents flickering.
Requires to be a void
method and has at least one parameter.
Enabled by default, can be disabled with the TiConfiguration
View Annotations only work because ThirtyInch supports interceptors.
Add interceptors (BindViewInterceptor
) to TiActivity
or TiFragment
to intercept the binding process from TiView
to TiPresenter
.
Interceptors are public API waiting for other great ideas.
public class HelloWorldActivity extends TiActivity<HelloWorldPresenter, HelloWorldView>
implements HelloWorldView {
public HelloWorldActivity() {
addBindViewInterceptor(new LoggingInterceptor());
}
}
LoggingInterceptor
is available as module and logs all calls to the view.
Using Kotlin these days is a no-brainer.
ThirtyInch
provides some extension methods to improve itself even further!
When using sendToView
, repeating it.*
inside the lambda is quite annoying.
It's clear that the methods are called on the view.
With the kotlin extension deliverToView
the TiView
will be give over to the lambda as this
.
class HelloWorldPresenter : TiPresenter<HelloWorldView> {
override fun onCreate() {
// normal java API
sendToView {
it.showText("Hello World")
}
// kotlin extension
deliverToView {
showText("Hello World")
}
}
}
interface HelloWorldView : TiView {
fun showText(text: String)
}
Back in the Java days we had to use it
inside the sendToView
-lambda.
If you're using Kotlin's Coroutines we offer a CoroutineScope
that scopes to a presenter's lifecycle.
class HelloWorldPresenter : TiPresenter<HelloWorldView> {
private val scope = TiCoroutineScope(this, Dispatchers.Default)
override fun onCreate() {
scope.launch { ... }
}
}
The created Job
will automatically be cancelled when the presenter is destroyed.
Alternatively, you can launch jobs that get cancelled when a TiView
detaches:
class HelloWorldPresenter : TiPresenter<HelloWorldView> {
private val scope = TiCoroutineScope(this, Dispatchers.Default)
override fun onAttachView(view: HelloWorldView) {
scope.launchUntilViewDetaches { ... }
}
}
However, be careful that launchUntilViewDetaches
can only be called when there is a view attached!
Using RxJava for networking is very often used.
Observing a Model
is another good usecase where Rx can be used inside of a TiPresenter
.
The Rx package provides helper classes to deal with Subscription
or wait for an attached TiView
.
public class HelloWorldPresenter extends TiPresenter<HelloWorldView> {
// add the subscription helper to your presenter
private RxTiPresenterSubscriptionHandler rxHelper = new RxTiPresenterSubscriptionHandler(this);
@Override
protected void onCreate() {
super.onCreate();
// automatically unsubscribe in onDestroy()
rxHelper.manageSubscription(
Observable.interval(0, 1, TimeUnit.SECONDS)
// cache the latest value when no view is attached
// emits when the view got attached
.compose(RxTiPresenterUtils.<Long>deliverLatestToView(this))
.subscribe(uptime -> getView().showPresenterUpTime(uptime))
);
}
@Override
protected void onAttachView(@NonNull final HelloWorldView view) {
super.onAttachView(view);
// automatically unsubscribe in onDetachView(view)
rxHelper.manageViewSubscription(anotherObservable.subscribe());
}
}
You can make Disposable
handling even less intrusive in Kotlin. Just create the following interface and make your presenters implement it:
interface DisposableHandler {
// Initialize with reference to your TiPresenter instance
val disposableHandler: RxTiPresenterDisposableHandler
// Dispose of Disposables dependent on the TiPresenter lifecycle
fun Disposable.disposeWhenDestroyed(): Disposable = disposableHandler.manageDisposable(this)
// Dispose of Disposables dependent on the TiView attached/detached state
fun Disposable.disposeWhenViewDetached(): Disposable = disposableHandler.manageViewDisposable(this)
}
Then just implement the interface in your presenter and you can use created extension functions to manage Disposable
s:
class MyPresenter : TiPresenter<MyView>(), DisposableHandler {
override val disposableHandler = RxTiPresenterDisposableHandler(this)
override fun onCreate() {
super.onCreate()
// Presenter lifecycle dependent Disposable
myObservable
.subscribe()
.disposeWhenDestroyed()
}
override fun onAttachView(view: MyView) {
super.onAttachView(view)
// View attached/detached dependent Disposable
myViewObservable
.subscribe()
.disposeWhenViewDetached()
}
}
Copyright 2016 grandcentrix GmbH
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.