a MVP library for Android favoring a stateful Presenter

Overview

DEPRECATED - no longer actively maintained

Build Status License Gitter

ThirtyInch - a MVP library for Android

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.

The name

Keep Android At Arm’s 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.

Story

Read the introduction article on Medium

See the slides of the latest talk on Speakerdeck

Get it

GitHub Packages

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"
}

JCenter (deprecated)

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"
}

Hello World MVP example with ThirtyInch

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

ThirtyInch features

Presenter

  • survives configuration changes
  • survives when the Activity got killed in background
  • is not a singleton
  • dies when the Activity gets finished
Lifecycle

The 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

Configuration

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

TiView Annotations

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

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

@DistinctUntilChanged

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 binding interceptors

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.

Kotlin

Using Kotlin these days is a no-brainer. ThirtyInch provides some extension methods to improve itself even further!

SendToView

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.

Coroutines

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!

RxJava

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 Disposables:

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

License

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.
Comments
  • How to use sendToView ?

    How to use sendToView ?

    I'm trying to call a method in my View through sendToView. But it is not working ? I have tried using getView. It returns NPE because i'm calling the getProductsByCat after the Presenter onAttach

    public void getProductsByCat(String Category) {
    //        final BrowseView view = getView();
            this.sendToView(BrowseView::showProgress);
    
            rxHelper.manageSubscription(
                    RxFirebaseDatabase.observeValueEvent(mRef,
                            dataSnapshot -> {
                                List<Product> mProd = new ArrayList<>();
                                for (DataSnapshot postSnapshot : dataSnapshot.getChildren()) {
                                    Timber.d("Count >>" + postSnapshot.getChildrenCount());
                                    Product Prod = Product.create(postSnapshot);
                                    mProd.add(Prod);
                                }
                                return mProd;
                            })
                            .subscribe(mProd -> {
                                Timber.d(mProd.get(0).productName());
                                sendToView(view -> {
                                    view.hideProgress();
                                    view.showText("Hello");
                                    view.updateItems(mProd);
                                });
                            })
            );
        }
    
    question 
    opened by AruLNadhaN 19
  • Feature: Lint Checks

    Feature: Lint Checks

    Description

    This implementation adopts and updates #111. Fixes #100

    The updates include:

    • Using the now stable Lint version to use UAST
    • Bundles the Lint checks with TI
    • Fixing the detection scope so Android Studio will report the error in the editor
    • Creating an own TI Lint Category
    • TiIssue is now a sealed class
    • some Kotlin tweaks

    How to Test

    Play with the following scenarios:

    • Remove the implements HelloWorldView in the HelloWorldActivity of the sample app
    • Remove the implements HelloWorldView but override provideView() in the HelloWorldActivity of the sample app
    • Use TI v0.9.1-SNAPSHOT of this branch in another project and have an TiActivity which does not implement the given TiView subclass
    Ready for Review lint 
    opened by jreehuis 12
  • Presenter not destroyed when Fragment is popped off the backstack

    Presenter not destroyed when Fragment is popped off the backstack

    When the back key is pressed to pop a TiFragment off the backstack, the Presenter does not get destroyed. Here is the log output from TiFragment:

    11-02 09:17:04.985 V: onStop()
    11-02 09:17:04.985 V: onDestroyView()
    11-02 09:17:04.986 V: onDestroy() recreating=true
    11-02 09:17:04.986 V: onDetach()
    

    Because the activity is not being destroyed, the destroyPresenter flag in TiFragment#onDestroy() does not get set to true. This means that the Presenter is leaked.

    bug thirtyinch 
    opened by GrahamBorland 12
  • Add TiDialogFragment

    Add TiDialogFragment

    Speaks for itself. Although I can think of prettier, delegate based, solutions to add MVP support for DialogFragments this is a simple copy from TiFragment but it extends android.support.v4.app.DialogFragment instead of android.support.v4.app.Fragment

    opened by remcomokveld 11
  • Bugfix: Presenter not destroyed when Fragment is popped off the backstack

    Bugfix: Presenter not destroyed when Fragment is popped off the backstack

    Hello everyone,

    currently I'm working on a fix for #33. We thought it would be better if we add all relevant Unit-Tests first so it would be easier to develop the bugfix. Additionally I've created a new Activity for the "Sample" application which can be be used to interactively test the Fragment lifecycle under different circumstances (e.g. back stack or configuration change).

    I would like to open this PR as early as possible to make it open for discussion.

    Please feel free to comment and contribute!

    Ready for Review 
    opened by ghost 10
  • How to Inject Presenter Dependency to Activity ?

    How to Inject Presenter Dependency to Activity ?

    Hey, I'm trying to convert this Library using ThirtyInch.

    This is how my Presenter looks like This is the Original Library's Presenter.

    Now I have to inject the DataManager dependency in my Activity. This is my BrowseActivity.

    This is the Original Library's BrowseFragment (For Reference)

    How can I inject the Dependency in the Activity or there is any workaround ?

    opened by AruLNadhaN 10
  • add extension functions for boilerplate-less Disposable handling

    add extension functions for boilerplate-less Disposable handling

    Create Kotlin helper interface DisposableHandler to be implemented by TiPresenter to provide syntactic sugar for Disposable handling.

    Implementing this interface by your Presenter enables you to call .disposeWhenDestroyed() and .disposeWhenViewDetached() as extension functions directly on your Disposable instances.

    Usage:

    class MyPresenter : TiPresenter<MyView>(), DisposableHandler {
    
       // configure with reference to your Presenter instance
        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()
        }
    }
    
    opened by lukaszkalnik 9
  • Move travis to docker

    Move travis to docker

    Based on #115

    • Do two build environments: unit and instrumentation (run in parallel)
    • Moved unit test to docker (because of the andoird images you can't really start a emulator in docker)

    Benefits of using docker:

    • The docker image contains only the "sdkamanger". The sdkmanager install every build-tools or android images if they needed automatically. We don't need to update the travis.yml anymore \o\
    • We can trust the docker. If the underlined host (travis <-> trusty, zenty) changed, everything just works as before. As long as we can run docker in that environment.

    Benefits of the new travisscript:

    • It parses automatically the BUILD_TOOLS_VERSION and the SDK_VERSION from the build.gradle file and put it into the the travis android components. We don't need to update the travis.yml anymore /o/

    Do we have speed benefits?

    • Unfortunately not really. Because travis is very slow with starting a emulator and stuff it won't decrease the build time. Maybe 10 seconds or so because travis don't run the unit tests anymore :)

    What do test:

    • If codecov works as aspected -> Checked ✅ works
    Ready for Review 
    opened by StefMa 9
  • Added Lint Check for missing TiView implementation

    Added Lint Check for missing TiView implementation

    This is an implementation based on the proposal made in #100. It introduces custom lint checks for ThirtyInch with a UAST-based Detector that seeks to point out illegal configuration of TiActivity, TiFragment, CompositeActivity & CompositeFragment sub-classes. Basically, it works like this:

    • Visit every non-abstract sub-class of either of the classes mentioned above
    • Retrieve the TiView sub-interface that the sub-class is connected to
    • Validate that the sub-class implements this TiView by checking its signature

    Note that there is an exception to this rule for Ti- classes, wherein a user can override TiViewProvider#provideView() to deviate from the default behaviour. The Detector verifies that, and doesn't report an issue on sub-classes that also override this method.

    Integration with CompositeAndroid works in a more low-level fashion, where the default constructor of the Composite- sub-class is analyzed for its registration of a TiPlugin via addPlugin(). If found, the TiView is extracted from there.

    Please take a close look at the behaviour of the Detector, and also at potential loopholes inside the unit tests.

    Ready for Review 
    opened by mannodermaus 8
  • ThirtyInch Lint Checks

    ThirtyInch Lint Checks

    ThirtyInch has quickly become my favourite MVP implementation. Still, and I guess it's mostly due to my forgetful mind, nearly every time I implement a new Ti-based Activity, connect everything & run, I encounter the dreaded error I've grown so accustomed to: java.lang.IllegalArgumentException: This Activity doesn't implement a TiView interface. This is the default behaviour. Override provideView() to explicitly change this.

    I'd like to propose some custom Lint checks that facilitate the daily workflow with ThirtyInch. I'm unsure how feasible this would be, especially since you allow this default behaviour to be changed on a per-Activity basis. The error would ideally be triggered on TiActivity & CompositeActivity sub-classes with the TiActivityPlugin applied, if no TiView interface is implemented.

    Again, I'm probably the only one oblivious enough to always forget this step, and maybe the effort outweighs the benefit by a long shot.

    PR welcome 
    opened by mannodermaus 8
  • [Question] Where is the best place to start loading data and persist it?

    [Question] Where is the best place to start loading data and persist it?

    I am currently investigating different MVxx libraries and approaches on Android and stumbled upon this one. As a show case for each library I am creating simple list+detail (image gallery) app using REST services as data provider.

    Lets assume we are using one Activity (MainActivity) and two fragments (GalleryFragment, DetailsFragment) in our app. Implementation of activity is quite straightforward as it requires no view or presenter - is simply starts and manages fragments.

    Now, we need to implement GalleryFragment which needs to display a list of images.

    So we need a view:

    public interface GalleryView extends TiView {
        void startLoading(boolean pullToRefresh);
        void stopLoading();
        void showError(Throwable error);
        void showGallery(List<Image> images);
    }
    

    We need layout for fragment:

    <?xml version="1.0" encoding="utf-8"?>
    <FrameLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginLeft="16dp"
        android:layout_marginRight="16dp">
    
        <ProgressBar
            android:id="@+id/progress"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:indeterminate="true"/>
    
        <android.support.v4.widget.SwipeRefreshLayout
            android:id="@+id/contentView"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
    
            <android.support.v7.widget.RecyclerView
                android:id="@+id/recyclerView"
                android:name="pl.fzymek.tiimagegallery.gallery.GalleryFragment"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                tools:context="pl.fzymek.tiimagegallery.gallery.GalleryFragment"
                tools:listitem="@layout/fragment_gallery_item"/>
    
    
        </android.support.v4.widget.SwipeRefreshLayout>
    
        <TextView
            tools:text="Error happened! :("
            android:id="@+id/error"
            android:layout_gravity="center"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
    
    </FrameLayout>
    

    And we need a Fragment:

    public class GalleryFragment extends TiFragment<GalleryPresenter, GalleryView> implements GalleryView, SwipeRefreshLayout.OnRefreshListener {
    
        @BindView(R.id.recyclerView)
        RecyclerView recyclerView;
        @BindView(R.id.progress)
        ProgressBar progressBar;
        @BindView(R.id.error)
        TextView error;
        @BindView(R.id.contentView)
        SwipeRefreshLayout contentView;
    
        GalleryAdapter adapter;
        Unbinder unbinder;
    
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                 Bundle savedInstanceState) {
            return inflater.inflate(R.layout.fragment_gallery, container, false);
        }
    
        @Override
        public void onViewCreated(View view, Bundle savedInstanceState) {
            unbinder = ButterKnife.bind(this, view);
            contentView.setOnRefreshListener(this);
    
            Context context = view.getContext();
            if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
                recyclerView.setLayoutManager(new LinearLayoutManager(context));
            } else {
                recyclerView.setLayoutManager(new GridLayoutManager(context, 3));
            }
            adapter = new GalleryAdapter();
            recyclerView.addItemDecoration(new SpaceDecoration());
            recyclerView.setAdapter(adapter);
        }
    
        @Override
        public void onDestroyView() {
            super.onDestroyView();
            unbinder.unbind();
        }
    
        @NonNull
        @Override
        public GalleryPresenter providePresenter() {
            Timber.d("providePresenter");
            return new GalleryPresenter();
        }
    
        @Override
        public void startLoading(boolean pullToRefresh) {
            progressBar.setVisibility(View.VISIBLE);
            contentView.setVisibility(View.GONE);
            error.setVisibility(View.GONE);
            contentView.setRefreshing(pullToRefresh);
        }
    
        @Override
        public void stopLoading() {
            contentView.setVisibility(View.VISIBLE);
            progressBar.setVisibility(View.GONE);
            error.setVisibility(View.GONE);
            contentView.setRefreshing(false);
        }
    
        @Override
        public void showError(Throwable err) {
            error.setVisibility(View.VISIBLE);
            contentView.setVisibility(View.GONE);
            progressBar.setVisibility(View.GONE);
            contentView.setRefreshing(false);
        }
    
        @Override
        public void showGallery(List<Image> images) {
            adapter.setData(images);
        }
    
        @Override
        public void onRefresh() {
            loadData(true);
        }
    
        private void loadData(boolean pullToRefresh) {
            getPresenter().loadData(pullToRefresh);
        }
    }
    

    And finally, we also have our presenter:

    class GalleryPresenter extends TiPresenter<GalleryView> {
    
        private final static TiConfiguration PRESENTER_CONFIGURATION = new TiConfiguration.Builder()
                .setRetainPresenterEnabled(true)
                .build();
    
        private GettyImagesService service;
    
        GalleryPresenter() {
            super(PRESENTER_CONFIGURATION);
            Retrofit retrofit = new Retrofit.Builder()
                    .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                    .addConverterFactory(GsonConverterFactory.create())
                    .baseUrl(Config.GETTYIMAGES_API_URL)
                    .build();
    
    
            service = retrofit.create(GettyImagesService.class);
        }
    
        @Override
        protected void onCreate() {
            super.onCreate();
            Timber.d("onCreate");
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            Timber.d("onDestroy");
        }
    
        @Override
        protected void onSleep() {
            super.onSleep();
            Timber.d("onSleep");
        }
    
        @Override
        protected void onWakeUp() {
            super.onWakeUp();
            Timber.d("onWakeUp");
        }
    
        public void loadData(String phrase, boolean pullToRefresh) {
            getView().startLoading(pullToRefresh);
    
            Timber.d("loadData %s", phrase);
    
            service.getImages(phrase)
                    .subscribeOn(Schedulers.newThread())
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribe(
                            result -> {
                                getView().showGallery(result.getImages());
                                getView().stopLoading();
                            },
                             error -> {
                                 getView().showError(error);
                             }
                    );
        }
    }
    

    Simple as that.

    What I do not understand, is when should I call loadData() to trigger a network request, and how and when to persist retrieved data between screen orientation changes.

    Usually we'd start network request somewhere in onViewCreated() or onCreateView method of fragment as in that moment we have our view ready. Unfortunately, when using ThirtyInch, this in not true, as view is binded to presenter in onStart() method and calling GalleryPresenter#loadData() throws NPE as getView() returns null.

    Solution to this would be to defer it a little and load data in onStart() method of fragment or onWakeUp() method of presenter (as presented in sample, but when using onWakeUp() I cannot pass my constructed query to the presenter). This works like a charm, but...

    Since I am retaining a presenter, I see a problem with this solution when rotating a screen while network request is pending. Flow is in this case:

    1. Fragment/View and Presenter gets created
    2. Fragment is started (onStart) and Presenter is woken up onWakeUp -> trigger request 1
    3. Screen gets rotated
    4. Fragment/View is recreated but Presenter is retained (ok)
    5. Fragment is started (onStart) and Presenter is woken up onWakeUp -> trigger request 2
    6. Request 1 finishes -> view shows data 1
    7. Rotate screen again
    8. We go back here to recreating view, retaining presenter and triggering another request(no 3)
    9. Now we have view displaying progress, although data was fetched (request 1) and displayed for a while and 2 pending requests which will trigger new view updates

    What I don't understand here is, how to correctly request data and persist it using ThirtyInch - is the persistence handled by framework (setRetainPresenterEnabled(true) ?) or should I rely on androids onSaveInstanceState(). Not retaining a presenter would leave me nothing more that Mosby (ad described here). So what should I do in order to use all features of TI?

    question 
    opened by fzymek 8
  • Migrate away from JCenter

    Migrate away from JCenter

    I'm sure you're well aware of this already, but we've got until May to republish the ThirtyInch artifacts on Maven Central.

    https://jfrog.com/blog/into-the-sunset-bintray-jcenter-gocenter-and-chartcenter/

    opened by GrahamBorland 1
  • Restore old BackstackReader.isInBackstack() implementation once AndroidX Fragment 1.2.1 is released

    Restore old BackstackReader.isInBackstack() implementation once AndroidX Fragment 1.2.1 is released

    There is a bug in AndroidX Fragment 1.2.0 which crashes the app when changing orientation in fragments or when switching fragments. This is because of our BackstackReader.isInBackstack() hacky implementation. https://github.com/sockeqwe/mosby/issues/318#issuecomment-577660091

    A temporary workaround has been implemented for this bug here: https://github.com/grandcentrix/ThirtyInch/pull/205 However, the workaround uses reflection so our previous hack for this problem has to be restored once AndroidX Fragment 1.2.1 gets released (this release will fix the current bug): https://issuetracker.google.com/issues/148189412

    future-release 
    opened by lukaszkalnik 1
  • Refactoring: Lint Name and Stub Extraction

    Refactoring: Lint Name and Stub Extraction

    Description

    How to Test

    As only some "strings" got extracted it should be sufficient to rely on the tests.

    If you want to test this anyways: Follow the test instructions of #187 and #188

    Merge Information

    This PR builds on top of #188 and transitive on #187 so please do not merge this after #188 and #187 . It will also ease the PR review a lot.

    Note

    Testing Lint Checks in the Sample Project did not work reliably for me. This seems to be an Android Studio Issue as quite often an Invalidate Caches and Restart help. A commandline lint run were reporting the errors reliably.

    Ready for Review lint 
    opened by jreehuis 0
  • Feature: Annotation Lint Checks

    Feature: Annotation Lint Checks

    Description

    Annotations in Java can be restricted to some languages types like Method or Class. In ThirtyInch some of our annotations so are allowed but are useless and will not trigger the job for what they are intended. This PR will bring Lint Checks will warn the dev about those cases right at the annotation usage.

    How to Test

    Play with the following scenarios:

    • Add a return type to fun showText(text: String) in the SampleView of the sample app
    • Add @DistinctUntilChanged and a return type to fun showText(text: String) in the SampleView of the sample app
    • Add @DistinctUntilChanged remove all parameters from fun showText(text: String) in the SampleView of the sample app
    • Add @DistinctUntilChanged to any method in an arbitary class which is not an interface
    • Add @CallOnMainThread to any method in an arbitary class which is not an interface
    • Add @DistinctUntilChanged to any method in an arbitary class which does not extends TiView
    • Add @CallOnMainThread to any method in an arbitary class which does not extends TiView
    • Use TI v0.10.0-SNAPSHOT of this branch in another project where the cases above might be existing or produce them

    Note: Always do a rebuild if you do a change in the Sample Project as it might has to be recompiled to pick the lint checks up.

    Merge Information

    This PR builds on top of #187 so please do not merge this after #187 . It will also ease the PR review a lot.

    Note

    Testing Lint Checks in the Sample Project did not work reliably for me. This seems to be an Android Studio Issue as quite often an Invalidate Caches and Restart help. A commandline lint run were reporting the errors reliably.

    Ready for Review lint 
    opened by jreehuis 0
  • Bugfix: Lint Checks not Triggering

    Bugfix: Lint Checks not Triggering

    Description

    This PR will solve a couple of issues with the implemented Lint Checks for TI.

    1. TI Lint Checks are not executed prior Build Tools 3.3 Caused by the lately increased the Build Tools version and so the Lint API Version while using com.android.tools.lint.detector.api.CURRENT_API as Lint Version. So the checks when marked as not compatible with the Build Tools used.
    2. Check if provideView() is overridden always returns true Caused by the fact that this check was checking the whole inheritance tree and so found the implementation in the TI base classes.
    3. Check if the View Interface is implemented did not check more than the direct super class

    How to Test

    Play with the following scenarios:

    • Remove the implements HelloWorldView in the HelloWorldActivity of the sample app
    • Remove the implements HelloWorldView but override provideView() in the HelloWorldActivity of the sample app
    • Use TI v0.10.0-SNAPSHOT of this branch in another project and have an TiActivity which does not implement the given TiView subclass

    Note

    Testing Lint Checks in the Sample Project did not work reliably for me. This seems to be an Android Studio Issue as quite often an Invalidate Caches and Restart help. A commandline lint run were reporting the errors reliably.

    Ready for Review lint 
    opened by jreehuis 0
Releases(v1.0.1)
  • v1.0.1(Jan 28, 2020)

  • v1.0.0(Jan 28, 2020)

  • v0.9.6(Jun 6, 2019)

    Version 0.9.6 06.06.19

    Summary

    Same as 0.9.5 but with an fix that TiCoroutineScope.launchUntilViewDetaches crashes when using in in TiPresenter.onAttachView.

    Changelog

    • Fix: Crash while using TiCoroutineScope.launchUntilViewDetaches #192 (Thanks @Syex!)

    Diff

    https://github.com/grandcentrix/ThirtyInch/compare/v0.9.5...v0.9.6

    Source code(tar.gz)
    Source code(zip)
  • v0.9.5(Jun 3, 2019)

    Version 0.9.5 03.06.19

    Summary

    Lint module

    New lint module to detect issues early. For instance we detect that you have missed to implement the TiView in your Activity or Fragment.

    Use: No action required. We bundle the lint registry together with the main thirtyinch module.

    kotlin-coroutines support

    This new kotlin module which makes the usage of Kotlin coroutines super easy with ThirtyInch.

    The Jobs you will start with the TiCoroutineScope will be automatically canceled when either the View disappeared or the Presenter got destroyed.

    Use: implementation "net.grandcentrix.thirtyinch:thirtyinch-kotlin-coroutines:0.9.5"

    You can also checkout our README#coroutines.

    plugin

    This module was removed because CompositeAndroid isn't maintained anymore.

    Changelog

    • New: lint module #158
    • New: kotlin-coroutines support #182
    • Deleted the plugin #168

    Diff

    https://github.com/grandcentrix/ThirtyInch/compare/v0.9.0...v0.9.5

    Source code(tar.gz)
    Source code(zip)
  • v0.9.0(Jul 31, 2018)

    Version 0.9.0 31.7.18

    Summary

    sendToView -> deliverToView for kotlin users

    New kotlin module which makes the usage of sendToView easier. If you have more ideas open a issue or PR.

    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.

    // normal java API
    sendToView {
        it.showText("Hello World")
    }
          
    // kotlin extension
    deliverToView {
        showText("Hello World")
    }
    

    Back in the Java days we had to use it inside the sendToView-lambda.

    Use: implementation "net.grandcentrix.thirtyinch:thirtyinch-kotlin:$thirtyinchVersion"

    AndroidX

    Starting with Android Studio 3.2 you can migrate to AndroidX and ThirtyInch is ready.

    Changelog

    • New: kotlin module with TiPresenter#deliverToView(block: View.() -> Unit) which can be used as a replacement for sendToView #150
    • Changed: AppCompat is now a dependency (api, was compileOnly)
    • New: Rx2TiPresenterUtils#deliverLatestToView #137 (thx @GrahamBorland)
    • Changed: TiPresenter#sendToView is now public (was protected) which allows a kotlin extension function #142
    • New: AndroidX support #153

    Diff

    https://github.com/grandcentrix/ThirtyInch/compare/v0.8.5...v0.9.0

    Source code(tar.gz)
    Source code(zip)
  • v0.8.5(Oct 14, 2017)

    Version 0.8.5 14.10.17

    Summary

    Mostly a maintenance release improving documentation, adding tests and cleaning up the repository.

    Highlight: The test package is now deprecated and all functionality was moved into the thirtyinch artifact.

    Thanks for external contributors: @baltuky @vpondala

    Public changes

    • Pull 115 New: Update dependencies (support lib 26.0.0, target- and compileSdkVersion 26 and other minor updates)
    • Pull 120 New: TiPresenter#test() returns a TiTestPresenter with helper methods for unit testing. Replaces TiPresenterInstructor and deprecates the test artifact.
    • Pull 126 Fix: TiPresenter#test() documentation, thx @baltuky

    Internal changes

    • Pull 99 Fix: remove javadoc warnings
    • Pull 103 New: Improved RELEASE.md instructions
    • Pull 105 New: Deprecate internal DelegatedTiActivity#isActivityChangingConfigurations which turned out as not required to handle config changes correctly. Simplifies internal logic
    • Pull 112 New: Update to gradlew 4.1 allowing development with Android Studio 3.0
    • Pull 113 New: Test for LifecycleObserver cleanup
    • Pull 114 New: Tests for internal OneTimeRemovable
    • Pull 117 New: internal PresenterScope tests
    • Pull 121 New: Replace all Hamcrest assertions with AssertJ, thx @vpondala
    • Pull 123 New: Add codestyle to project and reformat all sources according to this. Reduces diffs for external contributors
    • Pull 127 New: Additional tests for TiTestPresenter

    Diff

    https://github.com/grandcentrix/ThirtyInch/compare/v0.8.0...v0.8.5

    Source code(tar.gz)
    Source code(zip)
  • v0.8.0(May 4, 2017)

    Version 0.8.0 04.05.17

    Summary

    Version 0.8.0 is a big step forward towards 1.0.0. The biggest problem, a memory leak when using Fragments could be resolved. This bugfix was only possible after introducing tons of tests. API enhancements of TiPresenter make MVP even easier and removes common pitfalls

    Increased code coverage from below 20% to 67% (646/951 lines)

    Thanks for external contributors: @remcomokveld, @vRallev, @jonathan-caryl, @k0shk0sh

    thirtyinch

    TiPresenter

    • Pull 26 New: onAttachView(TiView) replaces onWakeUp() which is now deprecated
    • Pull 26 New: onDetachView() replaces onSleep() which is now deprecated
    • Pull 26 New: getView() is now annotated with @Nullable
    • Pull 87 New: getViewOrThrow() for cases where the view logically can't be null
    • Pull 36 New: sendToView(ViewAction<TiView>) to postpone code execution until the view is attached
    • Pull 65 New: runOnUiThread(Runnable) executes code on the view Ui Thread.
    • Pull 65 New: sendToView(view -> { }) automatically executes the action on the Ui Thread
    • Pull 94 Always call observer for events which happend when they were registered

    TiFragment

    • Pull 78 Presenter gets correctly destroyed when Fragment is not managed by the FragmentManager anymore
    • Pull 78, Pull 67 uses now the default: setRetainInstanceState(false). Setting TiFragment#setRetainInstanceState(true) will throw
    • Pull 78 Support for backstack

    TiActivity

    • Pull 78 Always use PresenterSavior singleton, drop support for NonConfigurationInstance

    Other

    • Pull 14, Pull 15 TiLog is used for logging. Listener to see a log output
    • Pull 19, Pull 24 @DistinctUntilChanged supports multiple comparators. (EqualsComparator, WeakEqualsComparator, HashComparator(default))
    • Pull 23 AppCompat is now included with provided dependency instead of compile
    • Pull 42 New: TiDialogFragment
    • Pull 79 New: @CallSuper where a super call is required.
    • Pull 79 New: restrict TiActivity, TiFragment... API for subclasses.
    • Pull 78, Pull 33, Pull 83, Pull 68 Fix: TiPresenter gets destroyed when TiFragment gets removed from the FragmentManager
    • Pull 81 New: Proguard rules included in the library
    • Pull 78 TiConfiguration#setUseStaticSaviorToRetain(Boolean) is now deprecated. The PresenterSavior singleton is always used to retain presenters when TiConfiguration#setRetainPresenterEnabled(true).

    rx

    • Pull 27 Fix: view can be null before unsubscribing from Subscriptions
    • Pull 43 New: manage\[View\]Subscriptions(Subscription...)
    • Pull 58, Pull 61 manageViewSubscription(Subscription) will now throw when the view isn't attached
    • Pull 61 RxTiPresenterUtils#isViewReady() now emits the ready event after onAttachView(TiView) was called.
    • Pull 73 New: manage\[View\]Subscription will now return Subscription

    rx2

    New module for RxJava2 analog to rx module

    • Pull 54, Pull 64 New: RxTiPresenterDisposableHandler#manageDisposable and RxTiPresenterDisposableHandler#manageViewDisposable

    plugin

    test

    • Pull 65 TiPresenterInstructor automatically sets an Executor for runOnUiThread and sendToView actions

    logginginterceptor

    • Pull 85 New: Add logging interceptor module including LoggingInterceptor

    Diff

    https://github.com/grandcentrix/ThirtyInch/compare/v0.7.1...v0.8.0

    Source code(tar.gz)
    Source code(zip)
  • v0.8.0-rc4(Apr 25, 2017)

    2017-04-25

    Most likely the last RC before releasing 0.8.0 🎉

    thirtyinch

    • Bugfix: TiPresenter gets destroyed when TiFragment gets removed from the FragmentManager #78 #33 #83 #68
    • New: TiFragment supports the backstack #78
    • New: restrict TiActivity, TiFragment... API for subclasses. #79
    • New: @CallSuper where a super call is required. #79
    • Deprecation: TiConfiguration#setUseStaticSaviorToRetain(Boolean) is now deprecated. The Savior singleton is always used to retain presenters when TiConfiguration#setRetainPresenterEnabled(true). This also means that TiActivity dropped support for the NonConfigurationInstance API #78
    • Change: TiFragment was initially using setRetainInstanceState(true). TiFragment uses now the default: setRetainInstanceState(false). When you try to set TiFragment#setRetainInstanceState(true) it will throw. #78 #67
    • New: No hard checks required to detect "Don't keep Activities", destroying the TiPresenter works now with the Activity API only. #78

    rx

    • New: manage*Subscription will now return Subscription #73
    • Change: manage*Subscription(Subscription...) is now manage*Subscriptions(Subscription...) (note the S here), was introduced in an earlier RC and is no breaking change. #73

    rx2

    • New: manage* Disposable will not return Disposable #73
    • Change: manage* Disposable(Disposable...) is now manage* Disposables(Disposable...) (note the S here 😉), was introduced in an earlier RC and is no breaking change. #73

    sample

    • New: retrolambda support #69 thx @jonathan-caryl
    • New: FragmentLifecycleActivity to test Fragments #78
    • New: LifecycleViewPagerActivity to test Fragments in a ViewPager #84

    New: logginginterceptor

    • New: LoggingInterceptor logs all method calls to the TiView interface to TiLog for debugging. Don't forget to enable TiLog or pass a custom Logger to the LoggingInterceptor constructor. #85

    compile "net.grandcentrix.thirtyinch:thirtyinch-logginginterceptor:$thirtyinchVersion"

        // register TiLog in your Application
        if (BuildConfig.DEBUG) {
            TiLog.setLogger(TiLog.LOGCAT);
        }
    
    public class MyActivity extends TiActivity<MyPresenter, MyView> implements MyView {
    
        public MyActivity() {
            // log by default to TiLog
            addBindViewInterceptor(new LoggingInterceptor());
    
           // log to logcat for debug builds
            addBindViewInterceptor(new LoggingInterceptor(BuildConfig.DEBUG ? TiLog.LOGCAT : null));
        }
    }
    

    Diff

    https://github.com/grandcentrix/ThirtyInch/compare/v0.8.0-rc3...v0.8.0-rc4

    Source code(tar.gz)
    Source code(zip)
  • v0.8.0-rc3(Feb 10, 2017)

    2017-02-10

    thirtyinch

    • New: TiPresenter#runOnUiThread(Runnable) executes code on the view Ui Thread. #65
    • New: sendToView(view -> { }) automatically executes the action on the Ui Thread #65
    • Fix: Change sendToView(action) actions will be executed after #onAttachView(view), and after all LifecycleObserver received their events. This allows preparing the view in onAttachView(TiView) for those actions. That way the view should be in a "running" state as if the view was never gone. #65
    • Fix: Change LifecycleObservers get called in order they are added for constructive events (VIEW_ATTACHED) and in reversed order for destructive events (VIEW_DETACHED, DESTROYED). First in, last out. #65

    plugin

    • New TiFragmentPlugin #49

    rx

    • New: manageViewSubscription(Subscription) will now throw when the view isn't attached #58, #61
    • Fix: Rename second parameter of TiLifecycleObserver#onChange(state, beforeLifecycleEvent) to TiLifecycleObserver#onChange(state, hasLifecycleMethodBeenCalled), also adjusted the documentation #61
    • Fix: Change RxTiPresenterUtils#isViewReady() to emit the ready event after onAttachView(TiView) was called.

    New: rx2

    • New: rx2 module compile "net.grandcentrix.thirtyinch:thirtyinch-rx2:$thirtyinchVersion" #54
    • New: auto dispose your Disposable with RxTiPresenterDisposableHandler#manageDisposable and RxTiPresenterDisposableHandler#manageViewDisposable #54, #64
    • RxTiPresenterUtils#isViewReady(): Observable<Boolean> emits the view attached state

    test

    • New: TiPresenterInstructor automatically sets an Executor for runOnUiThread and sendToView actions. It's even more recommended to use it instead of calling the lifecycle methods manually
    Source code(tar.gz)
    Source code(zip)
  • v0.8.0-rc2(Dec 16, 2016)

    Changelog 0.8.0-rc2

    thirtyInch

    • New: TiDialogFragment #42 with TiDialogFragmentDelegate under the hood #47 (thanks @vRallev)
    • New: @DistinctUntilChanged uses now hashcode() instead of equals() by default (again) for comparison. Revert of change introduced in 0.8.0-rc1 #46

    rx

    • New: Multiple subscriptions #43
    Source code(tar.gz)
    Source code(zip)
  • v0.8.0-rc1(Nov 9, 2016)

    Changelog 0.8.0-rc1

    thirtyinch

    New: TiPresenter lifecycle methods got renamed #26

    The new naming is more convenient throughout most android MPV libraries.

    • onWakeUp() is now deprecated and can be replaced with onAttachView(TiView) which has the TiView as argument
    • onSleep() is now deprecated and can be replaced with onDetachView()
    -    @Override
    -    protected void onWakeUp() {
    -        super.onWakeUp();
    -    }
    +    @Override
    +    protected void onAttachView(@NonNull final HelloWorldView view) {
    +        super.onAttachView(view);
    +    }
    
    -    @Override
    -    protected void onSleep() {
    -        super.onSleep();
    -    }
    +    @Override
    +    protected void onDetachView() {
    +        super.onDetachView();
    +    }
    

    New: TiPresenter#getView() is now @Nullable #26

    getView() is now annotated with @Nullable and causes lint to hint a Nullable warning. This warning was missing and caused a lot of NPEs for Ti beginners. On the other hand, the view in onAttachView(TiView) is @NonNull. No unnecessary null checks are required

    New: wait for view before executing code TiPresenter#sendToView(ViewAction<TiView>) #36

    Sending events to the view although the view isn't attached is already possible with rx and .compose(rxUtils.deliverLatestCacheToView(this)) which caches all items until the view is attached and emits them in the same order. The same behavior is now back ported to pure java.

    // TiPresenter
    @Override
    protected void onCreate() {
        super.onCreate();
        doNetworkCall(result -> {
            // getView() could be null
            // getView().showData(result);
    
            sendToView(view -> {
                // lambda will be executed when the view got attached (onAttachView(TiView))
                // view is never null
                view.showData(result);
            });
        });
    }
    

    New: TiLog logging API #14, #15

    Before this library logged everything automatically. ThirtyInch is now silent by default. If you are interested in the logs register your own logger.

    // logcat example
    TiLog.setLogger(TiLog.LOGCAT);
    
    // Timber example
    Timber.plant(new Timber.DebugTree());
    TiLog.setLogger(new TiLog.Logger() {
        @Override
        public void log(final int level, final String tag, final String msg) {
            Timber.tag(tag).log(level, msg);
        }
    });
    

    New: @DistinctUntilChanged uses now equals() for comparison instead of hashCode() (thx @fzymek) #19, #24

    The new default implementation uses equals() instead of hashcode(). A DistinctComparator can be set as argument of the annotation @DistinctUntilChanged(comparator = EqualsComparator.class). Three different implementations exist:

    • EqualsComparator (default) - uses equals() for comparison and holds a strong reference to the previous arguements
    • WeakEqualsComparator - uses equals() for comparison and holds a weak reference to the previous arguments. Could call a method twice when gc() kicks in.
    • HashComparator - old implementation using hashCode() for comparison. Doesn't hold references to the arguments

    New: The support library is now a provided instead of a compile dependency #23

    Therefore it uses the AppCompat version you are compiling in your app. It isn't required anymore to exclude the support library for all Ti dependencies when your app is using an older AppCompat version.

    Fix: Activity#recreate() (with useStaticSaviorToRetain=false) destroys the Presenter #35

    When changing the configuration of an TiActivity without closing it (by calling recreate() or changing the orientation) the Presenter was destroyed although the Activity flag isChangingConfigurations was true resulting in a NPE when the TiActivity runs in onCreate. This only happened when useStaticSaviorToRetain was set to false.

    thirtyinch-rx

    Fix: view can be null before unsubscribing from Subscriptions #27

    When using the RxTiPresenterSubscriptionHandler to automatically unsubscribe from views it can happen (multithreaded environment) that getView() returns null before Subscriptions#unsubscribe() is called.

    Source code(tar.gz)
    Source code(zip)
  • v0.7.1(Sep 7, 2016)

  • v0.7.0(Sep 4, 2016)

    ThirtyInch

    • remove RxJava dependency and moved it in a separate module
    • TiLifecycleObserver for the Presenter
    • TiConfiguration for disabling features can be passed into the TiPresenter constructor
    • upgrade to support library 24.2.0
    • moved TiActivity logic in TiActivityDelegate to share most code between the plugin and the Activity implementation
    • providePresenter() removed the Bundle argument which made the Presenter not reusable between Fragment and Activity. The Bundle is still available with getIntent().getExtras() but null checks are yours now.

    Rx

    is now a separate module

    • RxTiPresenterUtils contains Transformers delaying emits until the TiView is attached
    • RxTiPresenterSubscriptionHandler added to the TiPresenter manages subscriptions (see the HelloWorldPresenter)

    Plugin

    Test

    is a new module helping moving the presenter to the correct state in tests (TiPresenterInstructor)

    Source code(tar.gz)
    Source code(zip)
  • v0.6.0(Sep 4, 2016)

    ThirtyInch

    • Add Ti prefix to API classes to reduce clashes with android.view.View (TiView, TiPresenter, TiActivity, TiFragment)
    • statically typed Presenter
    -public class HelloWorldActivity extends ThirtyInchActivity<HelloWorldView>
    +public class HelloWorldActivity extends TiActivity<HelloWorldPresenter, HelloWorldView>
    
    • interface instead of abstract method for P providePresenter(), makes it easy for Kotlin
    • minSdkVersion 14
    • fragment presenter survives orientation change
    • Tests by @jveerkamp
    • NPE fix when calling bindNewView(null)

    Plugin

    • compositeAndroid 0.2.1 fixed onRetainNonConfigurationInstance not called
    Source code(tar.gz)
    Source code(zip)
  • v0.5.0(Sep 4, 2016)

Owner
HCI @ gcx
Human-Computer Interaction (HCI) Competence Center @ grandcentrix
HCI @ gcx
Nucleus is an Android library, which utilizes the Model-View-Presenter pattern to properly connect background tasks with visual parts of an application.

Nucleus Deprecation notice Nucleus is not under develpment anymore. It turns out that Redux architecture scales way better than MVP/MVI/MVVM/MVxxx and

Konstantin Mikheev 2k Nov 18, 2022
Moxy is MVP library for Android

Moxy This Moxy repository is deprecated and no longer supported. Please migrate to the actual version of the Moxy framework at Moxy communuty repo. De

Arello Mobile 1.6k Dec 28, 2022
Android app built with MVP architectural approach and uses Marvel Comics API that allows developers everywhere to access information about Marvel's vast library of comics. :zap:

Villains & Heroes Android app built with MVP architectural approach and uses Marvel Comics API that allows developers everywhere to access information

André Mion 53 Jul 13, 2022
Minimal UI library for Android inspired by React

Anvil - reactive views for Android Anvil is a small Java library for creating reactive user interfaces. Originally inspired by React, it suits well as

null 1.4k Dec 23, 2022
A Mosby based VIPER library for Android

Moviper A Mosby based VIPER library for Android Why Moviper? You got tired because of fact that your Activities and Fragments were becoming god classe

Mateusz Koślacz 78 Nov 29, 2022
Crossword library for Android

Ararat Ararat is a crossword library for Android, written for and used by alphacross. It includes: Parsers for various formats CrosswordRenderer, whic

Akop Karapetyan 28 Oct 3, 2022
The most complete and powerful data-binding library and persistence infra for Kotlin 1.3, Android & Splitties Views DSL, JavaFX & TornadoFX, JSON, JDBC & SQLite, SharedPreferences.

Lychee (ex. reactive-properties) Lychee is a library to rule all the data. ToC Approach to declaring data Properties Other data-binding libraries Prop

Mike 112 Dec 9, 2022
A lightweight, good expandability Android library used for displaying different pages like loading, error, empty, timeout or even your custom page when you load a page

中文 | English LoadSir ?? ?? LoadSir是一个高效易用,低碳环保,扩展性良好的加载反馈页管理框架,在加载网络或其他数据时候,根据需求切换状态页面, 可添加自定义状态页面,如加载中,加载失败,无数据,网络超时,如占位图,登录失效等常用页面。可配合网络加载框架,结合返回 状态

KingJA 3.3k Dec 21, 2022
Android part of the Android Studio(IntellijIDEA) OkHttp Profiler plugin

OkHttpProfiler Android Library Created by LocaleBro.com - Android Localization Platform The OkHttp Profiler plugin can show requests from the OkHttp l

Ievgenii 261 Dec 8, 2022
Android common lib, include ImageCache, HttpCache, DropDownListView, DownloadManager, Utils and so on

android-common-lib 关于我,欢迎关注 微博:Trinea 主页:trinea.cn 邮箱:trinea.cn#gmail.com 微信:codek2 主要包括:缓存(图片缓存、预取缓存、网络缓存)、公共View(下拉及底部加载更多ListView、底部加载更多ScrollView、

Trinea 5k Dec 30, 2022
dexposed enable 'god' mode for single android application.

What is it? Dexposed is a powerful yet non-invasive runtime AOP (Aspect-oriented Programming) framework for Android app development, based on the work

Alibaba 4.5k Dec 28, 2022
A small, yet full-featured framework that allows building View-based Android applications

Conductor A small, yet full-featured framework that allows building View-based Android applications. Conductor provides a light-weight wrapper around

BlueLine Labs 3.9k Jan 6, 2023
A Job Queue specifically written for Android to easily schedule jobs (tasks) that run in the background, improving UX and application stability.

This Project is Deprecated! Thanks to everybody who've used Android Priority JobQueue. It was designed in a world where there was no JobScheduler, RxJ

Yigit Boyar 3.4k Dec 31, 2022
A plugin system that runs like a browser, but instead of load web pages, it load apk plugins which runs natively on Android system.

Android Dynamic Loader Android Dynamic Loader is a plugin system. The host application is like a browser, but instead of load web pages, it load plugi

Tu Yimin 1.4k Dec 28, 2022
LiteOrm is a fast, small, powerful ORM framework for Android. LiteOrm makes you do CRUD operarions on SQLite database with a sigle line of code efficiently.

#LiteOrm:Android高性能数据库框架 A fast, small, powerful ORM framework for Android. LiteOrm makes you do CRUD operarions on SQLite database with a sigle line

马天宇 1.5k Nov 19, 2022
🚀Plugin for Android Studio And IntelliJ Idea to generate Kotlin data class code from JSON text ( Json to Kotlin )

JsonToKotlinClass Hi, Welcome! This is a plugin to generate Kotlin data class from JSON string, in another word, a plugin that converts JSON string to

Seal 2.8k Jan 3, 2023
Kick-starts Android application development.

Synopsis If you've made it here, chances are you are not quite as satisfied with the Android application framework as you could be. Same for us, that'

Matthias Käppler 1.3k Dec 4, 2022
Rosie is an Android framework to create applications following the principles of Clean Architecture.

Rosie The only way to make the deadline—the only way to go fast—is to keep the code as clean as possible at all times. — Robert C. Martin in Clean Cod

Karumi 1.8k Dec 28, 2022
Create kotlin android project with one line of command.

README This is an android application template project built with kotlin language and some useful libraries. It provides a creator script to quickly c

nekocode 1.6k Dec 20, 2022