Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Utilities for your full stack kotlin apps.

NotificationsYou must be signed in to change notification settings

shibasis0801/flatInvoker

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Libraries for scalable cross-platform development based on Kotlin Multiplatform.Useful abstractions and utilities to build tech better.Reaktor is the root library, and FlatInvoker focuses on using FlexBuffers to define foreign function interfaces.

for i inecho location work notification;do for j inls reaktor-$i/src; do mkdir reaktor-$i/src/$j/kotlin/dev/shibasis/reaktor/$i; done; done

FlatInvoker

FlatInvoker focuses on using FlexBuffers to perform foreign function invokations(ffi)

Application Areas:

  1. React Native: Native modules for React Native on mobile
  2. Hermes FFI: TypeScript modules for Native Code on mobile

Dependeasy

Dependeasy is a plugin designed to assist with multi-platform dependencies. It offers abstractions for setting up multiplatform projects quickly, provides CMake support for Kotlin/Native, and includes size benchmarking tools.

Benchmarking

Usereact-native-performance and kotlinx-benchmark for benchmarking.

Roadmapdocumentation.

Architecture

The architecture is meant to be powerful but as simple as it can be but no simpler.The primary idea is Separation of Concerns as long as it does not cause too much overhead.

General Units

Component

A component is the most basic unit of execution. It is lifecycle aware and gets events if the parent container's lifecycle changes (app goes to background, etc).

Adapter

An adapter tries to abstract away the platform specific implementation of a component and provide a common interface.

It holds a weak reference to the controller, and lets you get best of both worlds. We like to believe we can separate all our logic from platform specific behaviour but we can't. Most libraries for each platform need some platform dependencies. Storing these dependencies can cause memory leak issues and other problems.

Adapters solve this by providing a common interface but letting you access the platform specific implementation safely when you need to.

Adapter Capabilities

When we write an abstraction over platforms, it is natural that the common abstraction would be the least common denominator with only common behaviour. But sometimes we need to access platform specific behaviour.

Capabilities are sub-interfaces that can be implemented by individual platforms. This lets you write your UI logic in a platform agnostic way, but still access platform specific behaviour when you need to.

They feel like globals, and can have sub-interfaces. For example Camera support on Android and iOS can be different, so CameraAdapter would be

abstractclassCameraAdapter<Controller>(controller:Controller): Adapter<Controller>(controller) {enumclassCameraStart {Success,ControllerFailure,PermissionFailure,CameraFailure,    }abstractsuspendfunstart():CameraStart    @ComposableabstractfunRender()// Optional CapabilityinterfaceFileCapability {funstoreFile(name:String)    }// Optional CapabilityinterfaceAnalyserCapability {funaddAnalyser():Boolean    }// ...}classAndroidCameraAdapter(activity:ComponentActivity,valpermissionAdapter:PermissionAdapter<*>): CameraAdapter<ComponentActivity>(activity), CameraAdapter.FileCapability, CameraAdapter.AnalyserCapability// but, for web you may not have file capability.classWebCameraAdapter(webComponent:WebComponent,valpermissionAdapter:PermissionAdapter<*>): CameraAdapter<WebComponent>(webComponent), CameraAdapter.AnalyserCapability// You can easily check for capabilities using Kotlin's type systemfunuseCamera(cameraAdapter:CameraAdapter<*>) {if(cameraAdapterisCameraAdapter.AnalyserCapability) {        cameraAdapter.addAnalyser()    }if(cameraAdapterisCameraAdapter.FileCapability) {        cameraAdapter.storeFile("file")    }// ...}// See how the above lets you write UI code without bothering with platform checks.

Feature

A Feature accessed as Feature.Camera or something is an abstraction over platform functionality. You initialize them during App startup and they exist outside of DependencyInjection context.

It should generally be an Adapter, because ephemeral dependencies should be injected via Koin DI.

// Super simple declaration which feels like a Global.varFeature.Camera byCreateSlot<CameraAdapter<*>>()

Data Units

Result<T>s are used heavily in the architecture. They are the standard way to denote Success and Failure. try-catch prevents optimizations by compilers and also disrupts the flow of the code.

All of the following units are available from dependency injection (koin), so can be scoped as needed.

Api

Call your remote APIs / DBs / anything. You need to return a Result<T> indicating success or failure. Helper functions succeed / fail are provided.

// Adapter here, ensures swapping supabase is easy.// Ideally it should also abstract away library details, but need to be pragmatic and not idealistic.openclassFriendApi:SupabaseAdapter() {suspendfungetFriendChats(userId:Int)= invokeSuspendResult {val columns=Columns.raw("""    friend_id,    chat_id,    users!friends_friend_id_fkey1(email)""".trimIndent())        succeed(            from("friends")            .select(columns= columns)            {                filter {                    eq("user_id", userId)                }            }            .decodeAs<List<FriendFetch>>()        )    }}

Repository

A repository takes an Api and a ObjectDatabase. You create an ObjectStore from this database and use it to store and retrieve objects.

classFriendRepository(database:ObjectDatabase,valfriendApi:FriendApi,) {privateval friendStore=ObjectStore(database,"friend_store")suspendfungetFriendChats(userId:Int):Result<List<FriendFetch>> {val (data, _)= friendStore.writeThrough("$userId:friend_chats") {            friendApi.getFriendChats(userId)        }return data    }}// Look through the ObjectStore / ObjectDatabase code to understand how they function.

Interactor

An interactor is the bridge between UI and Data. It has repositories as dependencies and carries out business logic. It should hold transformations and can hold state, but try to keep them stateless.

It is also responsible to provide data in a way easy for UI to use (and so that UI can avoid LaunchedEffect, etc which should be used sparingly)

classChatInteractor(valfriendRepository:FriendRepository,valgroupChatRepository:GroupChatRepository,valmessageRepository:MessageRepository,valuserRepository:UserRepository) {// combines bothsuspendfunfetchChatList(userId:Int):List<ChatListItem>// uses userRepository, friendRepository & messageRepositoryprivatesuspendfunchats(userId:Int):List<ChatListItem>// uses userRepository, groupChatRepository & messageRepositoryprivatesuspendfungroupChats(userId:Int):List<ChatListItem>}

ViewModels

Ideally your UI should directly deal with stateless interactors, but if for some reason your UI needs state and also has complex interactions which don't make sense to put inside one interactor, or requires orchestrating multiple interactors.

You use a ViewModel. A ViewModel contains multiple interactors and orchestrates them.Avoid them as long as you can, as they have the risk of becoming god-classes and also letting you build monolithic god-ui functions. Both are generally bad unless really needed.

Routing Units

For routing, we have Screens, Switches and Containers. Each contains the former.

Route

This is the base class. It holds a sub-interface(similar to Adapter.Capability, but these are not Adapters).

sealedclassRoute(varpattern:RoutePattern =RoutePattern()) {var parent:Route?=nullvar container:Container?=nullinterfaceRender<outT:Props> {        @ComposablefunRender(props: @UnsafeVarianceT)    }}

Screen

A screen contains a Render function, and hosts a UI. It is a route which doesn't have its own navigation.

abstractclassScreen<outT:Props>(valdefaultProps:T): Route(), Route.Render<T> {funwith(props: @UnsafeVarianceT,varargparams:Pair<String,Any>)=ScreenPair(this, props.also {            params.forEach { pair->                it.params[pair.first]= pair.second.toString()            }        })funscreenPair()=with(defaultProps)}

Switch

A switch contains a bunch of screens and nested switches, but still does not have its own navigation.

classSwitch(valhome:Screen<Props> =ErrorScreen("HomeScreen not selected"),valerror:Screen<Props> =ErrorScreen(),privatevalbuilder:Switch.()->Unit = {}): Route()

Container

The meat of the framework, a container holds a Switch and multiple back stacks. This handles navigation inside of it. It also holds a Container UI which hosts the active Screen.

abstractclassContainer(valswitch:Switch): Route(), Route.Render<Props> {constructor(        home:Screen<Props>=ErrorScreen("Home Screen not selected"),        error:Screen<Props>=ErrorScreen(),        builder:Switch.()->Unit= {}    ):this(Switch(home, error, builder))abstractfunconsumesBackEvent():Booleanabstractfunpush(screenPair:ScreenPair)abstractfunreplace(screenPair:ScreenPair)abstractfunpop()}

You can extend this to create BottomNavigation, TabbedNavigation, etc

openclassSingleStackContainer(switch:Switch): Container(switch) {constructor(        home:Screen<Props>=ErrorScreen("Home Screen not selected"),        error:Screen<Props>=ErrorScreen(),        builder:Switch.()->Unit= {}    ):this(Switch(home, error, builder))privateval screenStack=ObservableStack<ScreenPair>()overridefunbuild() {super.build()        push(switch.home.screenPair())    }    @ComposableoverridefunRender(props:Props) {val current by screenStack.top.collectAsState()        current?.let {            it.screen.Render(it.props)        }    }overridefunconsumesBackEvent():Boolean {return screenStack.size>1    }overridefunpush(screenPair:ScreenPair) {        screenStack.push(screenPair)    }overridefunreplace(screenPair:ScreenPair) {        screenStack.replace(screenPair)    }overridefunpop() {        screenStack.pop()    }}

MultiStackContainer is also provided, but is a WIP and you would generally use a concrete class like BottomNavigationContainer, TabbedNavigationContainer, etc.

classBottomBarContainer(start:String,error:Screen<Props> =ErrorScreen(),builder:MultiStackContainer<BottomBarItem>.()->Unit = {}) : MultiStackContainer<BottomBarItem>(start, error, builder) {    @ComposableoverridefunRender(props:Props) {val currentKey by currentKey.collectAsState()Scaffold(            bottomBar= {NavigationBar {// We create an item for each key                    metadata.keys.forEach { key->NavigationBarItem(                            selected= currentKey== key,                            onClick= { switchStack(key) },                            icon= {Icon(metadata[key]!!.icon, key) },                            label= {Text(key) }                        )                    }                }            }        ) { innerPadding->Box(props.modifier, contentAlignment=Alignment.Center) {Content()            }        }    }}

Sample Usage

SingleStackContainer(startScreen) {            switch("profile", profileScreen) {                screen("{id}", friendProfileScreen)                screen("edit", editProfileScreen)            }            container("home",BottomBarContainer("Chats") {                        item(BottomBarItem("Chats",Icons.AutoMirrored.Filled.Chat),                            switch("chats", chatsScreen) {                                screen("{id}", chatScreen)                            })                        item(BottomBarItem("Campaigns",Icons.Filled.Campaign),                            screen("campaigns", campaignScreen)                        )                        item(BottomBarItem("Directs",Icons.Filled.People),                            container("directs",TabbedContainer("Groups") {                                item(TabBarItem("Groups"),                                    switch("groups", groupsScreen) {                                        switch("{id}", groupChatScreen) {                                            screen("members", groupMembersScreen)                                        }                                    }                                )                                item(TabBarItem("Friends"),                                    switch("friends", friendsScreen) {                                        screen("{id}", friendProfileScreen)                                    }                                )                            })                        )                    }            )        }

About

Utilities for your full stack kotlin apps.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

[8]ページ先頭

©2009-2025 Movatter.jp