Flawless
Flawless copied to clipboard
Composition and statically typed screens to workaround Android architecture flaws.
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.