A Kotlin Engineer’s Journey Building a Web Application with Flutter in Just One Month
A Kotlin Engineer’s Journey Building a Web Application with Flutter in Just One Month
Hello. I am Ohsugi from the Woven Payment Solution Development Group.
Our team generally engages in server-side programming with Kotlin/Ktor and is currently working at Woven by Toyota Inc. on the development of the payment system used in Toyota Woven City.
Working in cooperation with our in-house business teams and partners to build Woven City, we have repeatedly conducted proofs of concept (PoC) to expand the features of the payment system. Recently, we implemented a first PoC for the payment system, simulating its operation in actual retail stores.
In this article, I would like to introduce how we decided to adopt Flutter to develop client apps as part of the PoC.
Introduction
To conduct the PoC for retail sales operations, we developed the following features to support store functions in addition to the payment system:
- Product management for store inventory
- Point-of-sale (POS) cash registers, including:
- Product scanning
- Shopping cart functionality
- Sales reports and payment management
- Inventory tracking
In particular, to regularly update tens of thousands of product information items, report sales and conduct month-end inventory checks, we needed more than just a payment API —we required a GUI application accessible to non-technical store staff. This was what prompted us, who normally focuses on server-side development, to suddenly start the challenge to create a client application.
Selecting a Language and Framework
When developing the client application, we narrowed down our choices to a cross-platform framework that would allow for application development not only for the web but also for iOS/Android.
Language / framework | Reasons for selection |
---|---|
Dart / Flutter, Flutter on the web | - This is a trending technology that has been getting significant attention recently. - Having also been adopted by the in-house Mobile App Development Team, members across teams are very familiar with this language and framework. |
TypeScript / Expo (React Native), Expo for web | - In terms of web development, this choice would enable us to move forward with React, which is one of the most mature technologies out there. - Our team members have experience with React, so ramp-up time would be minimal. |
Kotlin / Compose Multiplatform, Compose for web | With few existing adoption examples, we have the opportunity to explore more innovative development approaches. - There are no team members with direct development experience, but it should be straightforward for those familiar with Kotlin. |
Technical validation
In order to select a language and framework, we conducted a technical evaluation by creating a web app that combines state management and screen transitions, which are important elements for client app development.
The app we created is very simple: pressing the +
or -
button increases or decreases a count on the screen on the left (Home Page), and then pressing the "next"
button navigates to the right screen (Detail Page), where the count is displayed.
For each language/framework combination, we looked at the differences in the development experience it would offer in terms of how UI components are implemented, performance, libraries, documentation, and community support.
How UI components are implemented
First, we compared Flutter on the web, Expo for web, and Compose for web using the Detail Page code on the right of the image above as an example.
Dart / Flutter on the web
- I find it very intuitive as you can implement the UI using object-oriented components rather than the DOM.
- You can use virtually the same code for both mobile and web apps.
- Material Design is applied by default for styling, which has its pros and cons, but is a real boon in situations where engineers need to handle design too.
- When rendering with Canvaskit, it's possible to achieve nearly identical UI appearance.
class DetailPage extends StatelessWidget {
const DetailPage({super.key});
Widget build(BuildContext context) {
final args =
ModalRoute.of(context)!.settings.arguments as DetailPageArguments;
return Scaffold(
appBar: AppBar(
title: const Text("Flutter Demo at Detail Page"),
),
body: Center(
child: ConstrainedBox(
constraints: const BoxConstraints(minWidth: 120),
child: Center(
child: Text(
args.value.toString(),
style: const TextStyle(fontSize: 72),
),
),
),
),
);
}
}
TypeScript/Expo
- Like Flutter, UI can be implemented with object-oriented components instead of the DOM, making it feel very intuitive.
- As the downside of this combination, the framework provides the minimum components, requiring you to implement additional ones on your own.
- The same code can be used for both mobile and web with minimal differences.
- Styling is done with StyleSheet, a syntax similar to CSS, you may not feel it is so difficult as you do with CSS as the scope applied to the app is limited.
- The sample app usesreact-navigation to implement screen transitions.
const DetailPage: React.FC = () => {
// from react-navigation
const route = useRoute<RouteProp<RootStackParamList, 'Detail'>>();
return (
<View>
<Header title={'Expo Demo at Detail Page'} />
<CenterLayout>
<Counter value={route.params.value}/>
</CenterLayout>
</View>
);
}
const Header : React.FC<{title: String}> = (props) => {
const {title} = props;
return (
<View style={styles.header}>
<Text style={styles.title}>
{title}
</Text>
</View>
)
}
const CenterLayout: React.FC<{children: React.ReactNode}> = (props) => {
const {children} = props;
return (
<View style={styles.layout}>
{children}
</View>
)
}
const Counter: React.FC<{value: number}> = (props) => {
const {value} = props;
return (
<View style={styles.counterLayout}>
<Text style={styles.counterLabel}>{value}</Text>
</View>
)
}
const styles = StyleSheet.create({
header: {
position: "absolute",
top: 0,
left: 0,
width: '100%',
backgroundColor: '#20232A',
padding: '24px 0',
},
title: {
color: '#61dafb',
textAlign: 'center',
},
layout: {
display: 'flex',
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
height: '100vh',
},
counterLayout: {
minWidth: 120,
textAlign: 'center'
},
counterLabel: {
fontSize: 72,
}
});
Kotlin / Compose for web
- Instead of using the Compose UI used on mobile and desktop, we implement the UI using web-specific components that wrap around the HTML DOM.
- Code cannot be reused across mobile and web
- Styling needs to be implemented in CSS.
- For component implementation, you can either define the properties of each component from scratch or use pre-defined components as StyleSheet objects.
- To implement screen transitions, the sample app uses the routing-compose library for Compose Multiplatform, which supports both web and desktop.
@Composable
fun DetailPage(router: Router, params: Map<String, List<String>>?) {
Div {
components.Header(title = "Compose for web Demo at Detail Page")
CenterLayout {
params?.get("value")?.get(0)?.let { Counter(it.toInt()) }
}
}
}
@Composable
fun Header(title: String) {
H1(attrs = {
style {
position(Position.Fixed)
top(0.px)
left(0.px)
paddingTop(24.px)
paddingBottom(24.px)
backgroundColor(Color("#7F52FF"))
color(Color("#E8F0FE"))
textAlign("center")
width(100.percent)
}
}) {
Text(title)
}
}
@Composable
fun CenterLayout(content: @Composable () -> Unit) {
Div(attrs = {
style {
display(DisplayStyle.Flex)
flexDirection(FlexDirection.Row)
justifyContent(JustifyContent.Center)
alignItems(AlignItems.Center)
height(100.vh)
}
}) {
content()
}
}
@Composable
fun Counter(value: Int) {
Span(attrs = {
style {
minWidth(120.px)
textAlign("center")
fontSize(24.px)
}
}) {
Text(value.toString())
}
}
Performance
Next, we compared the build times and bundle sizes for the sample app created with each language and framework. In building the app, we set optimization options by default. We set up the testing environment on a MacBook Pro 2021 with an M1 Pro CPU and 32 GB of memory.
Language / Framework | Build conditions | Build time | Bundle size |
---|---|---|---|
Dart / Flutter on the web | - Flutter v3.7.7 - Dart v2.19.2 |
14 s | 1.7 MB (CanvasKit) 1.3 MB (Html) |
Typescript / Expo for web | - TypeScript v4.9.4 - Expo v48.0.11 |
10 s | 500 KB |
Kotlin / Compose for web | - Kotlin v1.8.10 | 9 s | 350 KB |
As you can see, the bundle size for the sample app functionality with Flutter is about 10 times larger than that with React, which means that the initial rendering will probably take quite a long time. You can check the details of the JS code generated by Flutter by adding --dump-info
to the build options, which helped us to see that the code mainly contains the Dart and Flutter framework part.
Libraries, documentation, and community support
Lastly, I have put together some information on the libraries, documentation, and community support among other things, for each language-framework combination.
Language / Framework | Libraries | Documentation / Community support |
---|---|---|
Dart / Flutter on the web | With Flutter packages, you can search for libraries available for Flutter. Libraries marked with the Flutter Favorite logo are officially recognized for their popularity and ease of use. |
The official documentation and videos are comprehensive, and the website also provides recommended libraries and design guidelines for state management, etc. |
Typescript / Expo for web | The basic libraries are fairly extensive, and the de facto standard ones are also easy to find if you search for them. The maintenance of each library relies to a large extent on the community, so you need to be careful when choosing the most suitable language-framework combination for you. | For basic implementations, there is a rich selection of React official documentation and Expo official documentation . Regarding effective design guidelines, including library design ones, it looks good to us to refer to the React discussions on the web. |
Kotlin / Compose for web | You can use a wide variety of JVM libraries. However, Android and Compose UI-related libraries are often not available in Compose for web. | There is not very much documentation, so you need to either search the GitHub repositories, or search the community’s Slack channel for information. |
The Adoption of Flutter
Based on the technical evaluation described above, we chose Flutter as the technology stack for client app development in the PoC. The reasons are as follows:
- Even team members unfamiliar with client app development can easily work with Flutter, as it has comprehensive documentation and reference materials, shich should minimize the impact on our primary server-side development work.
- The framework is actively being developed and well-maintained, so it is easy to upgrade versions and introduce libraries.
- Given the characteristics of the PoC, the app will run in a stable network environment, so performance limitations are not a significant concern.
Additionally, though it may sound as an added justification, being able to run JavaScript on Dart was very reassuring when encountering issues that couldn't be solved with Flutter alone. Our system uses Keycloak as the authentication platform, and since Keycloak’s official repository does not currently provide a Flutter client library, we are handling the authentication by running a JS library on Dart.
Conclusion
In this article, I introduced the reasons behind our decision to adopt Flutter for the development of the client app used in the PoC. Currently, we are also developing the client app in parallel with our server-side development. We would like to update this blog with more information as we deepn our technical knowledge in the future.
関連記事 | Related Posts
We are hiring!
【Toyota Woven City決済プラットフォームフロントエンドエンジニア(Web/Mobile)】/Toyota Woven City Payment Solution開発G/東京
Toyota Woven City Payment Solution開発グループについて我々のグループはトヨタグループが取り組むToyota Woven Cityプロジェクトの一部として、街の中で利用される決済システムの構築を行います。Toyota Woven Cityは未来の生活を実験するためのテストコースとしての街です。
【Woven City決済プラットフォーム構築 PoC担当バックエンドエンジニア(シニアクラス)】/Toyota Woven City Payment Solution開発G/東京
Toyota Woven City Payment Solution開発グループについて私たちのグループはトヨタグループが取り組むWoven Cityプロジェクトの一部として、街の中で利用される決済システムの構築を行います。Woven Cityは未来の生活を実験するためのテストコースとしての街です。