• Stars
    star
    214
  • Rank 184,678 (Top 4 %)
  • Language
    Kotlin
  • License
    Apache License 2.0
  • Created almost 3 years ago
  • Updated 6 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Butterfly is a navigation framework based on Coroutine + Annotation + Ksp. Support navigation for Activity, Fragment, DialogFragment and Compose.

Butterfly

Butterfly - Small and powerful weapons, own it, let your Android are developed like Tiger, Carry whole game!

Only the mightiest and most experienced of warriors can wield the Butterfly, but it provides incredible dexterity in combat.

Item introduction: +30 Agility +35% Evasion +25 Attack Damage +30 Attack Speed

Read this in other languages: 中文, English, Change Log

Feature

The butterfly provides these features:

Support navigation to Activity
Support navigation to Fragment
Support navigation to DialogFragment
Support navigation to Compose UI
Support navigation to Action
Support navigation parameters pass and receive
Support navigation interceptor
Support Fragment and Compose UI backstack
Support Fragment and Compose UI group manage
Support Fragment and Compose UI launch mode,such as SingleTop、ClearTop
Support communicate between modules

Setup

repositories {
  maven { url 'https://jitpack.io' }
}
apply plugin: 'kotlin-kapt'

dependencies {
  implementation 'com.github.ssseasonnn.Butterfly:butterfly:1.2.3'
  kapt 'com.github.ssseasonnn.Butterfly:compiler:1.2.3'

  //for compose
  implementation 'com.github.ssseasonnn.Butterfly:butterfly-compose:1.2.3'
}

Basic Usage

Navigation

Butterfly supports navigation for Activity、Fragment and DialogFragment and Compose UI component

@Agile("test/activity")
class AgileTestActivity : AppCompatActivity()

@Agile("test/fragment")
class TestFragment : Fragment()

@Agile("test/dialog")
class TestDialogFragment : DialogFragment()

@Agile("test/compose")
@Composable
fun HomeScreen() {}

//Navigation
Butterfly.agile("test/xxx").carry(context)

//Navigation and get result
Butterfly.agile("test/xxx")
    .carry(context) {
        val result = it.getStringExtra("result")
        binding.tvResult.text = result
    }

Communication

Butterfly supports communication between any component

Define the interface in Module A and add Evade annotation:

@Evade
interface Home {
    fun showHome(fragmentManager: FragmentManager, container: Int)
}

Define the implementation in Module B, and add the EvadeImpl annotation:

//The implementation class name must end with Impl
@EvadeImpl
class HomeImpl {
    val TAG = "home_tag"

    //Implement the method in the Home interface. 
    //The method name and method parameters must be the same
    fun showHome(fragmentManager: FragmentManager, container: Int) {
        val homeFragment = HomeFragment()
        fragmentManager.beginTransaction()
            .replace(container, homeFragment, TAG)
            .commit()
    }
}

There is no dependency between Module A and Module B

The communication between Module A and B can be completed by Butterfly:

val home = Butterfly.evade<Home>()
home.showHome(supportFragmentManager, R.id.container)

Passing parameters

You can pass parameters during navigation in two ways:

  • Add parameters to scheme by splicing scheme
  • Manually pass in the parameters by calling the params method, or a combination of the two, and then obtain the corresponding parameters in the navigated page

Passing parameters:

//Splicing scheme
Butterfly.agile("test/scheme?a=1&b=2").carry(context)

//Call params
Butterfly.agile("test/scheme?a=1&b=2")
    .params("intValue" to 1)
    .params("booleanValue" to true)
    .params("stringValue" to "test value")
    .carry(context)

Parses parameters:

//On the navigation target page, you can obtain the passed parameter value through the key field of the parameter
@Agile("test/scheme")
class AgileTestActivity : AppCompatActivity() {
    val a by lazy { intent?.getStringExtra("a") ?: "" }
    val b by lazy { intent?.getStringExtra("b") ?: "" }
    val intValue by lazy { intent?.getIntExtra("intValue", 0) ?: 0 }
}
//In addition to manual parameter analysis, Bracer can also be equipped to realize fully automatic parameter analysis
@Agile("test/scheme")
class AgileTestActivity : AppCompatActivity() {
    val a by params<String>()
    val b by params<String>()
    val intValue by params<Int>()
}

See Github address for details of Bracer usage: Bracer

Interceptor

Butterfly supports global interceptors and one-time interceptors

//Custom Interceptor
class TestInterceptor : ButterflyInterceptor {
    override fun shouldIntercept(agileRequest: AgileRequest): Boolean {
        //Detect whether interception is required
        return true
    }

    override suspend fun intercept(agileRequest: AgileRequest): AgileRequest {
        //Processing interception logic
        println("intercepting")
        delay(5000)
        println("intercept finish")
        return agileRequest
    }
}

Configure Global Interceptors:

//Add Global Interceptor
ButterflyCore.addInterceptor(TestInterceptor())

//Skip all global interceptors
Butterfly.agile("test/scheme").skipGlobalInterceptor().carry(context)

Configure one-time interceptors:

//Only the current navigation uses this interceptor
Butterfly.agile(Schemes.SCHEME_AGILE_TEST)
    .addInterceptor(TestInterceptor())
    .carry(context)

Action

Butterfly supports not only page navigation, but also navigation actions. Actions have no pages and can be processed logically

First let the customized class inherit Action, then add Agile annotation and set scheme. The rest are consistent with page navigation

@Agile("test/action")
class TestAction : Action {
    override fun doAction(context: Context, scheme: String, data: Bundle) {
        Toast.makeText(context, "This is an Action", Toast.LENGTH_SHORT).show()
    }
}

//Start Action
Butterfly.agile("test/action").carry(context)

//Action also support params
Butterfly.agile("test/action?a=1&b=2").carry(context)

Butterfly.agile("test/action")
    .params("intValue" to 1)
    .carry(context)

Action does not support obtaining returned data

Using Flow

In addition to directly calling carry to complete navigation, you can also call flow or resultFlow to return to Flow

Butterfly.agile("test/scheme").flow()
    .onStart { println("start") }
    .onCompletion { println("complete") }
    .launchIn(lifecycleScope)

//or
Butterfly.agile("test/scheme").resultFlow()
    .onStart { println("start") }
    .onCompletion { println("complete") }
    .onEach { println("process result") }
    .launchIn(lifecycleScope)

Router Table

Butterfly will generate a routing table for each annotated module. The naming rule is: Butterfly[module name]Module

Manual registration:

class DemoApplication : Application() {
    override fun onCreate() {
        super.onCreate()

        ButterflyCore.addModule(ButterflyHomeModule())
        ButterflyCore.addModule(ButterflyFooModule())
        ButterflyCore.addModule(ButterflyBarModule())
    }
}

Automatic registration with plugin:

  1. Add plug-in dependency
//使用 plugins DSL:
plugins {
    id "io.github.ssseasonnn.butterfly" version "1.0.1"
}

//Or use the legacy plugin application:
buildscript {
    repositories {
        maven {
            url "https://plugins.gradle.org/m2/"
        }
    }
    dependencies {
        classpath "io.github.ssseasonnn:plugin:1.0.1"
    }
}

//Add plugin
apply plugin: "io.github.ssseasonnn.butterfly"
  1. Implement your own Application class
class DemoApplication : Application() {
    override fun onCreate() {
        super.onCreate()
    }
}

Extra Config

Activity Configuration

Butterfly.agile("test/activity")
    .clearTop()                  //Launch mode
    //or .singleTop()
    .addFlag(Intent.Flag_XXX)    //Add other Flag
    .enterAnim(R.anim.xxx)       //Add anim
    .exitAnim(R.anim.xxx)
    .carry(context)

Fragment Configuration

Butterfly.agile("test/fragment")
    .clearTop()                  //Fragment also supports launch mode
    //or .singleTop()
    .disableBackStack()          //Do not add to the backstack
    .enterAnim(R.anim.xxx)       //Add anim
    .exitAnim(R.anim.xxx)
    .container(R.id.container)   //Set the container ID to which Fragment is added
    .carry(context)

DialogFragment Configuration

Butterfly.agile("test/dialog")
    .disableBackStack()         //Do not add to the backstack
    .carry(context)

Dialog and Fragment retreat

Butterfly supports the backstack of Fragment and DialogFragment

Back the top page anywhere:

Butterfly.retreat()

//or
Butterfly.retreat("result" to "123")

Back itself inside the page:

@Agile("test/fragment")
class TestFragment : Fragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        btnBack.setOnClickListener {
            retreat()
            //or
            retreat("result" to "123")
        }
    }
}

@Agile("test/dialog")
class TestDialogFragment : DialogFragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        btnBack.setOnClickListener {
            retreat("result" to "123")
        }
    }
}

Fragment Group manage

In addition to using the stack to manage fragments, Butterfly also supports managing fragments in the form of a group.

For example, there are multiple tabs on the APP homepage, and each tab corresponds to a fragment

Butterfly.agile("test/fragment")
    .group("groupName")             //Pages that use the same groupName will be added to the same group
    .carry(context)

Pages with the same groupName will be added to the same group, and only one instance of each fragment will exist. When switching these fragments, hide and show methods will be used instead of add or replace

Compose UI Support

Butterfly also support Compose UI's navigation:

@Agile("test/compose")
@Composable
fun HomeScreen() {
    Box() {
        ...
    }
}

//navigate to HomeScreen
Butterfly.agile("test/compose").carry(context)

Compose UI Parameter pass also supports URL splicing and params:

Just add a bundle type parameter to the compose component to pass the parameters passed by through the Bundle access navigation process

@Agile("test/compose")
@Composable
fun HomeScreen(bundle: Bundle) {
    val a by bundle.params<Int>()
    Box() {
        Text(text = a)
    }
}

//Splicing scheme
Butterfly.agile("test/compose?a=1&b=2").carry(context)

//or use params
Butterfly.agile("test/compose?a=1&b=2")
    .params("intValue" to 1)
    .params("booleanValue" to true)
    .params("stringValue" to "test value")
    .carry(context)

Compose UI and ViewModel:

If you need to use ViewModel, you only need to add the corresponding ViewModel type to the compose component. Butterfly will automatically create ViewModel for you to use it.

@Agile("test/compose")
@Composable
fun HomeScreen(homeViewModel: HomeViewModel) {
    val textFromViewModel = homeViewModel.text.asFlow().collectAsState(initial = "")

    Box() {
        Text(text = textFromViewModel.value)
    }
}

//no other config
Butterfly.agile("test/compose").carry(context)

Use Bundle and ViewModel at the same time:

Butterfly can support the use of Bundle and ViewModel parameters at the same time, but it should be noted that the order must be bundle in front, ViewModel is behind

@Agile("test/compose")
@Composable
fun HomeScreen(bundle: Bundle, homeViewModel: HomeViewModel) {
    val a by bundle.params<Int>()
    val textFromViewModel = homeViewModel.text.asFlow().collectAsState(initial = "")

    Box() {
        Text(text = a)
        Text(text = textFromViewModel.value)
    }
}

//no other config
Butterfly.agile("test/compose?a=1&b=2").carry(context)

License

Copyright 2022 Season.Zlc

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.