kompass

Project Url: sellmair/kompass
Introduction: A powerful router concept for Android, written in Kotlin :heart:
More: Author   ReportBugs   
Tags:

Kompass


GitHub top language Build Status Bintray GitHub last commit GitHub commit activity the past week, 4 weeks, year Gitter

Support

I am happy to help you with any problem on gitter, as fast as I can!
Alternatively, just open a new issue!

Why you should use Kompass

  • Powerful router which works great with MVP, MVVM and any other architecture
  • Boilerplate free routing: No more bundle.putInt(ARG_SOMETHING, something)
  • Very simple and clean architecture for applying custom transitions
  • Generic fragment transitions
  • Routing with multiple screen (which are called ships)
  • Kotlin

Setup

Step 1: Enable Annotation Processing

Add this at the top of your build.gradle

apply plugin: 'kotlin-kapt'


kapt {
    generateStubs = true
}
Step2: Add Kompass Dependencies
dependencies {
    ...
    implementation 'io.sellmair:kompass:0.1.0-alpha.2'
    implementation 'io.sellmair:kompass-annotation:0.1.0-alpha.2'
    kapt 'io.sellmair:kompass-processor:0.1.0-alpha.2'
}

Usage

Example

I highly recommend having a look at the example app built with Kompass



Gif



Basic

Kompass uses a ship related naming schema. Here are the types that you will encounter

  • 🏰 Kompass is the upper most object and contains multiple ships

  • 🛶️ KompassShip is the entity which can route to a certain Destination. This might represent a certain area of your activity where fragments can be loaded: e.g. One Ship can route to views/fragments on the top of the screen while another Ship is able`to display content on the bottom of the screen. You can have as many ships as you want in your App.

  • KompassSail️ is the actual area where fragments can be placed in. Your activity therefore sets the sails for a certain ship, which then 'sails' to the destination. You might want to use FrameLayout most often as target for your fragments

  • 🏖 Destination represents one certain 'scene' of your app. It also holds all necessary arguments for the fragment/activity. For example: You might have a 'LoginDestination', 'HomeDestination', 'SettingsDestination', ... in your application. You can use plain kotlin (data) classes to represent destinations

  • 🗺 KompassMap knows how to display a certain Destination (meaning which Fragment/View/Activity to load for it). A map (AutoMap) is automatically created for you

  • 🏗 KompassCrane knows how to push a Destination object into a Bundle. A Cran (AutoCran) is automatically created for you

  • 🎢 Detour can implement custom transitions/animations for certain routes. Just implement a KompassFragmentDetour or KompassViewDetour

Create a Kompass

Creating the Kompass is very simple using the provided builder:

Create a Kompass: Trivial

This example is the most trivial Kompass that can be built. It accepts any object implementing KompassDestination as Destination. We will talk about the .autoMap() part later. It is easy, I promise :bowtie:

val trivialKompass = Kompass.builder<KompassDestination>()
                     .autoMap() // we will talk about this later
                     .build()



Create a Kompass: Real World Example

Here is a real-world example of Kompass, where MyCustomDestinationType is just a basic sealed class and 'autoMap', 'autoCrane' and 'autoPilot' are extension functions automatically generated by the KompassCompiler. But as you can see: It is very easy to create a Kompass object :blush:

val kompass = Kompass.builder<MyCustomDestinationType>()
                     .autoMap()
                     .autoCrane()
                     .autoDetour()
                     .build()

Create your Destinations

Destinations are simple classes or data classes which hold very simple data like

  • Float
  • Int
  • String
  • List<Float>
  • List<Int>
  • List<String>
  • FloatArray
  • IntArray
  • Array<String>
  • Parcelable
  • ...

(Everything that can be represented inside android.os.Bundle plus some additions)

Destinations are typically annotated with

@Destination(target = [MyFragmentOrActivity::class])

I personally consider it a good idea implementing a sealed superclass for groups of Destinations and restrict the Kompass object to this superclass.

Example: Annotated Destination
@Destination
class HomeDestination(val user: User)

Set sails to a Ship

Once your activity has started, you have to provide a sail for the ship which should route to certain destinations. You can do this whenever you want to (even after you routed your ship to a certain destination). The following example will show how the FrameLayout with id 'R.id.lisacontainer' will be used for the ship called Lisa as _Sail:

class MainActivity : AppCompatActivity() {

    @SuppressLint("MissingSuperCall")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // Step 1: Get the kompass instance (Dagger, Kodein, Singleton?, ...)
        val kompass = ...

        // Step 2: Get the ship called 'lisa'
        val lisa = kompass["lisa"]


    }

    override fun onStart(){
        super.onStart()
        // Step 3: Set the sail and release it automatically by lifecycle
        lisa.setSail(sail(R.id.lisa_container)).releasedBy(this)
    }
}

Route to a Destination

Now it is time to route to a certain destination. The following example will show how the routing for a login-screen could look like: Side-note: kompass.main is a little convenience extension for kompass["main"]


    @SuppressLint("MissingSuperCall")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val user = getUser()

        sail = kompass.main.setSail(this, container.id)
        kompass.main.navigateTo(if(user!=null) HomeDestination(user) else LoginDestination())

    }

Recreate Destination from Bundle

One of the strongest parts of Kompass is the elimination of hassle with bundles and arguments. You can easily recreate the original Destination from an intent or bundle using the automatically generated extension functions.

Example: Fragment

If you routed to a certain fragment you can easily recreate the destination from the arguments Bundle

class HomeFragment: Fragment() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val homeDestination = arguments.asHomeDestination() // Function was automatically generated
        val user = homeDestination.user
        // ... Do something with your user object
    }
}
Example: Activity
class HomeActivity: AppCompatActivity(){
    override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
        val homeDestination = intent.extras.asHomeDestination()
        val user = homeDestination?.user
        // ... Do something with your user object
    }
}

Advanced

🗺 The Map

Maps contain information about how to display a certain Destination. This can be done by starting a new Activity or creating a new Fragment. If you want to use a custom Map element, add it to the KompassBuilder

Kompass.builder()
       .addMap(myCustomMap)
       ...

🏗 The Crane

A crane knows how to pack a Destination object into a bundle. If you want to use a custom Cran, add it to the KompassBuilder

Kompass.builder()
       .addCrane(myCutomCrane)

🎢 The Detour

It is a very common thing to apply transitions when one fragment is replaced by another fragment. A Detour can very easily implement such a transition generically.

Consider we want every fragment to slide in, when entered and slide out, when exited. We just have to write a Detour class like this:

    @Detour
    class FragmentSlide: KompassFragmentDetour<Any, Fragment, Fragment>{
        override fun setup(destination: Any,
                           currentFragment: Fragment,
                           nextFragment: Fragment,
                           transaction: FragmentTransaction) {
            currentFragment.exitTransition = Slide(Gravity.RIGHT)
            nextFragment.enterTransition = Slide(Gravity.LEFT)
        }

    }

Every Detour will automatically be applied if the types of 'destination', 'currentFragment' and 'nextFragment' can be assigned from the current route and

Kompass.builder()
       .autoDetour() // <-- will be available if you have some class annotated with @Detour

is used!

AutoMap, AutoCrane, AutoPilot

The functions

Kompass.builder()
       .autoMap()
       .autoCran()
       .autoDetour()

are automatically generated if possible.

  • .autoMap() will be available after you specified one target for at least one @Destination
  • .autoCrane() will be available after you annotated at least one class with @Destination
  • .autoDetour() will be available after you annotated at least one class with @Detour

BackStack

Kompass comes with an own back-stack. You should override your Activities 'onBackPressed' like:

    override fun onBackPressed() {
        if (!kompass.backImmediate())
            finish()
    }

You can add custom elements to the back-stack by passing a lambda to Kompass

kompass.onBack {
  // do something
}
Support Me
Apps
About Me
Google+: Trinea trinea
GitHub: Trinea