Coder Social home page Coder Social logo

flawless's Introduction

Download

This library is an attempt to allow use of composition in Android (which is initially spoiled by no-arg constructors and reflection) and add static typing to Fragments by using generics instead of setArguments(Bundle) and onActivityResult(Intent).

repositories {
    ...
    maven { url 'https://dl.bintray.com/miha-x64/maven' }
}

implementation 'net.aquadc.flawless:flawless:0.0.7'

In Activity of parent Fragment, you implement ScreenFactory:

class MainActivity : AppCompatActivity(), ScreenFactory {

    ...

    fun createScreen(intent: AnyScreenIntent): AnyScreen = select(intent) {
        // composition: you can pass to constructor whatever you want
        RootScreenTag then { RootScreen(args, Companion::openDialogFragment, DialogScreenTag) }
        DialogScreenTag then { DialogScreen(args) }
        // 'args: ScreenArgs' contains argument, host (Fragment, DialogFragment, etc), and saved state
    }

    private companion object {

        private val RootScreenTag
                by tag(of<RootScreen>())

        private val DialogScreenTag
                by tag(of<DialogScreen>())
        //                ^ exact type of a screen

        fun openDialogFragment(host: Fragment, new: DialogFragment) {
            new.show(host.fragmentManager, null)
        }
    }

}

Passing data

class RootScreen(
        private val args: StatelessActionScreenArgs<SupportFragment>,
        private val openDialog: (Fragment, DialogFragment) -> Unit,
        private val questionScreenTag: SupportDialogFragScreenTag<ParcelString, ParcelString, *>
) : StatelessSupportFragScreen<ParcelUnit, ParcelUnit> {
//                             ^ input     ^ output

    ...

    private fun openDialog() {
        val host = args.host
        openDialog(host,
                SupportDialogFragment(
                        questionScreenTag, // screen tag
                        ParcelString(input!!.text.toString()), // argument
                        host, OpenDialogRequestCode, // target, requestCode
                        pureParcelFunction2(RootScreen::gotResponse), // response callback
                        pureParcelFunction1(RootScreen::onCancel) // cancellation callback
                )
        )
    }

    private fun gotResponse(string: ParcelString) {
        output.text = string.value
    }

    ...

}

...and delivering results:

class DialogScreen : StatelessSupportDialogFragScreen<ParcelString, ParcelString> {

    ...

    override fun createView(host: SupportDialogFragment<ParcelString, ParcelString>, parent: Context, argument: ParcelString): Dialog {
        
        ...

        return AlertDialog.Builder(parent)
                .setTitle(argument.value)
                .setView(view)
                .setPositiveButton("Ok") { _, _ ->
                    returnValue = ParcelString(view.text.toString())
                }
                .setNegativeButton("Cancel", null)
                .create()
    }

    // will be automatically delivered when this fragment finish
    override var returnValue: ParcelString? = null
            private set

}

Requesting permissions and starting activities

private fun takePhoto() {
    host.requestPermissions(
            RequestCameraPermCode,
            pureParcelFunction2(RootScreen::takePhotoPermResult),
            { _, userAgreed ->
                AlertDialog.Builder(host.activity)
                        .setMessage("We need permission to camera to do this.")
                        .setPositiveButton("Let's grant", { _, _ -> userAgreed.run() })
                        .setNegativeButton("Meh", null)
                        .show()
            },
            Manifest.permission.CAMERA
    )
}

private fun takePhotoPermResult(granted: Collection<String>) {
    if (Manifest.permission.CAMERA !in granted) {
        return host.toast("Camera permission was denied.")
    }

    val i = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
    if (i.resolveActivity(host.activity.packageManager) == null) {
        return host.toast("Can't find app for taking pictures.")
    }

    host.registerRawResultCallback(TakePhotoRequestCode, pureParcelFunction3(RootScreen::photoTaken))
    host.startActivityForResult(i, TakePhotoRequestCode)
}

private fun photoTaken(responseCode: Int, data: Intent?) {
    host.toast(when (responseCode) {
        Activity.RESULT_OK -> "OK"
        Activity.RESULT_CANCELED -> "Canceled"
        else -> "response code: $responseCode"
    })
}

Screen as a suspend-function

Screen consumes some arguments and returns a value after interacting with a user. Thus it can be treated as a suspend-function.

Library does not provide any coroutine-based APIs at the moment because continuations cannot be serialized yet: #76 in kotlinx.coroutines, #44 in kotlinx.serialization.

You can check out a sample flow which should not be used in production because it cannot handle process death.

flawless's People

Contributors

miha-x64 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

flawless's Issues

Crash when several fragments getting closed

Imagine such situation: fragment A is in backstack, fragment B is running and targeting fragment A.
Both are getting popped at the same time.
Fragment B wants to deliver cancellation because it dies. But it calls onActivityResult on dead (destroyed, presenter == null) fragment A.

What should happen?

Result delivery is broken

When a sub fragment was started with setCustomAnimations(4 args), automatic result delivery works correctly. If sub fragment removal happens without animation, target.isFinishing is true, and result delivery is being skipped.

Host-fragment should have visibility state

...like None (no view), Hidden (view created, but userVisibleHint == false), Visible (view created, userVisibleHint == true).
Ability to subscribe on these events.
Ability to register code which will be run when view gets focused first time.

Add delivery strategy

Screen should have control over result delivery, i. e. it may deliver result after onDestroy (default) or earlier.

Introduce general-purpose solutions

Introduce:

  • A presenter with RecyclerView, ProgressBar, and error view, intended for loading data. Deferred loading with setUserVisibleHint.
  • Some generic dialogs
  • A full-screen fragment with a progressBar
  • more...

Document them.

Deep type reification

Take presenter type checking seriously. For example, use Guava's TypeToken in debug builds.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.