Header image

Xử Lý Lỗi Theo Phong Cách Lập Trình Hàm Trong Kotlin Với Arrow-kt

07/04/2022

1.21k

Xử Lý Lỗi Theo Phong Cách Lập Trình Hàm Trong Kotlin Với Arrow-kt

Bài viết này sẽ giới thiệu một số cách để xử lý lỗi trong Kotlin theo phong cách lập trình hàm sử dụng thư viện Arrow-kt. Những ví dụ được đưa ra sẽ theo thứ tự từ đơn giản đến phức tạp nhưng mạnh mẽ hơn.

Kotlin là gì?

Kotlin là một ngôn ngữ lập trình kiểu tĩnh, ban đầu được thiết kế để chạy trên máy ảo Java (JVM), sau này có thể biên dịch sang JavaScript và Native binaries sử dụng công nghệ LLVM. Kotlin có cú pháp hiện đại, ngắn gọn, an toàn và hỗ trợ cả lập trình hướng đối tượng (OOP)lập trình hàm (FP).

Arrow-kt là gì?

Arrow-kt (https://arrow-kt.io/) là một thư viện Typed Functional Programming trong Kotlin. Arrow cung cấp một ngôn ngữ chung của các interface và sự trừu tượng hóa trên các thư viện Kotlin. Nó bao gồm các kiểu dữ liệu phổ biến nhất như Option, Either, Validated, v.v … và các functional operator như traversecomputation blocks giúp cho người dùng viết các ứng dụng và thư viện pure FP dễ dàng hơn.

Setup

Trong file build.gradle.kts của root project, thêm mavenCentral vào danh sách:

allprojects {
    repositories {
        mavenCentral()
    }
}

Thêm dependency vào file build.gradle.kts của project:

val arrow_version = "1.0.1"
dependencies {
    implementation("io.arrow-kt:arrow-core:$arrow_version")
}

Pure function và exceptions

Pure function

Trong lập trình hàm, pure function là những function có hai tính chất:

  • Giá trị trả về chỉ phụ thuộc vào tham số truyền vào nó (tức là nếu cùng input thì cùng output).
  • Không tạo ra các side effect.

Side effect là những tác dụng xảy ra khi thực hiện một function mà không phải công dụng chính của nó. Ví dụ: ngoài việc trả về các giá trị, nó gây ra những tương tác thay đổi môi trường, thay đổi các biến toàn cục, thực hiện các hoạt động I/O như HTTP request, in dữ liệu ra console, đọc và ghi files Trong Kotlin, tất cả các function trả về Unit rất có thể tạo ra side effect. Đó là bởi vì giá trị trả về là Unit biểu thị “không có giá trị hữu ích được trả về”, điều này ngụ ý rằng function không làm gì khác ngoài việc thực hiện các side effect.

Một số ví dụ pure function trong Kotlin là những hàm toán học như sin, cos, max, …

Lợi ích của pure functions: dễ dàng combine, dễ dàng test, dễ dàng debug, dễ dàng parallelize, … Vì thế trong lập trình hàm, chúng ta sẽ cố gắng sử dụng nhiều pure functions nhất có thể, và tách biệt các phần pure và impure.

Exceptions

Kotlin có thể throwcatch các Exception tương tự như ngôn ngữ Java, JavaScript, C++,… Sử dụngtry { } catch(e) { } finally { } là cách xử lý lỗi phổ biến trong các ngôn ngữ lập trình mệnh lệnh.

Tuy nhiên việc throw và catch các Exception, chúng ta có thể thay đổi hành vi của function, khiến cho các function không còn pure nữa (catch Exception là một side effect). Ví dụ, một function catch hai Exception là ex1ex2 từ một function khác và tính toán kết quả, lúc đó kết quả đó sẽ phụ thuộc vào thứ tự thực thi của các câu lệnh, thậm chí có thể thay đổi giữa hai lần thực thi khác nhau của cùng một hệ thống.

Partial function

Ngoài ra, việc throw các Exception khiến cho các function trở thành một Partial function, tức là một function không hoàn toàn – không được xác định cho tất cả các giá trị input có thể có, bởi vì trong một số trường hợp, nó có thể không bao giờ trả về bất cứ thứ gì. Ví dụ, trong trường hợp một vòng lặp vô hạn hoặc nếu một Exception được throw.

Ví dụ: findUserById ở ví dụ dưới là một partial function.

@JvmInline
value class UserId(val value: String)

@JvmInline
value class Username(val value: String)

@JvmInline
value class PostId(val value: String)

data class User(
    val id: UserId,
    val username: Username,
    val postIds: List<PostId>
)

class UserException(message: String?, cause: Throwable?) : Exception(message, cause)

/**
 * @return an [User] if found or `null` otherwise.
 * @throws UserException if there is any error (eg. database error, connection error, ...)
 */
suspend fun findUserById(id: UserId): User? = TODO()

Đề làm cho findUserById trở thành một total function, chúng ta thay vì throw UserException, chúng ta có thể return nó như một giá trị, thay return type của findUserById thành UserResult.

sealed interface UserResult {
    data class Success(val user: User?) : UserResult
    data class Failure(val error: UserException) : UserResult
}

suspend fun findUserById(id: UserId): UserResult = TODO()

Các vấn đề với Exceptions

Exception có thể được xem như là những câu lệnh GOTO như trong C/C++, vì chúng làm gián đoạn luồng chương trình bằng cách quay lại nơi gọi nó. Các Exception không nhất quán, đặc biệt là khi trong lập trình Multithread, chúngta try...catch một function nhưng Exception được throw ở một Thread khác mà không thể catch nó được.

Một vấn đề khác là việc lạm dụng catch Exception: catch nhiều hơn những gì cần thiết và cả những Exception từ hệ thống như VirtualMachineError, OutOfMemoryError,…

try {
    doExceptionalStuff() //throws IllegalArgumentException
} catch (e: Throwable) {
    // too broad, `Throwable` matches a set of fatal exceptions and errors
    // a user may be unable to recover from:
    /*
    VirtualMachineError
    OutOfMemoryError
    ThreadDeath
    LinkageError
    InterruptedException
    ControlThrowable
    NotImplementedError
    */
}

Và cuối cùng, nhìn vào một signature của một function, chúng ta không thể biết được, nó sẽ throw ra Exception nào, ngoài việc đọc docs hay là đọc source code của nó, thay vào đó, chúng ta hay để signature của function đó biểu hiện rõ ràng những lỗi nào có thể xảy ra khi gọi function đó.

Vì vậy, để xử lý lỗi, chúng ta cần một type có thể được compose với nhau, và biểu thị một kết quả hợp lệ hoặc một lỗi. Những type đó là Discriminated union/ tagged union, trong Kotlin đó được triển khai thông qua sealed class/sealed interface/enum class. Chúng ta sẽ cùng tìm hiểu kotlin.Result được cung cấp bởi Kotlin Sdtlib từ version 1.3 , và sau đó là arrow.core.Either đến từ thư viện Arrow-kt.

Sử dụng kotlin.Result để xử lý lỗi

Chúng ta có thể sử dụng Result<T> như là một type để biểu thị: hoặc là giá trị thành công với type là T, hoặc là là một lỗi xảy ra với một một Throwable. Nếu theo cách hiểu đơn giản, Result<T> = T | Throwable.

Để tạo ra giá trị Result, ta có thể dụng các function có sẵn như

  • Result.success
  • Result.failure
  • runCatching (tương tự như try { } catch { }
    nhưng trả về Result).
suspend fun findUserByIdFromDb(id: String): UserDb? = TODO()

fun UserDb.toUser(): User = TODO()

suspend fun findUserById(id: UserId): Result<User?> = runCatching { findUserByIdFromDb(id.value)?.toUser() }

Chúng ta có thể kiểm tra Result là giá trị thành công hay không, thông qua hai property là isSuccessisFailure. Để thực hiện các hành động ứng với mỗi trường hợp của Result thông qua function onSuccessonFailure.

val userResult: Result<User?> = findUserById(UserId("#id"))
userResult.isSuccess
userResult.isFailure
userResult.onSuccess { u: User? -> println(u) }
userResult.onFailure { e: Throwable -> println(e) }

Để có thể lấy giá trị bên trong Result, chúng ta sử dụng các function getOr__. Sử dụng exceptionOrNull để truy cập giá trị Throwable bên trong nếu Result đại diện cho giá trị thất bại. Ngoài ra, còn có function fold có thể handle một trong hai case dễ dàng.

val userResult: Result<User?> = findUserById(UserId("#id"))

// Access value
userResult.getOrNull()
userResult.getOrThrow()
userResult.getOrDefault(defaultValue)
userResult.getOrElse { e: Throwable -> defaultValue(e) }

// Access Throwable
userResult.exceptionOrNull()

fun handleUser(u: User?) {}
fun handleError(e: Throwable) {
    when (e) {
        is UserException -> {
            // handle UserException
        }
        else -> {
            // handle other cases
        }
    }
}

userResult.fold(
    onSuccess = { handleUser(it) },
    onFailure = { handleError(it) }
)

Tuy nhiên, sức mạnh thực sự của Result nằm ở việc chain các hoạt động trên nó. Ví dụ: nếu bạn muốn truy cập một property của User:

val userResult: Result<User?> = findUserById(UserId("#id"))
val usernameNullableResult: Result<Username?> = userResult.map { it?.username }

Chú ý rằng, nếu việc gọi lambda truyền vào function map throw Exception, thì Exception đó sẽ bị throw ra ngoài. Nếu muốn Exception đó được catch và chuyển thành giá trị Result, sử dụng mapCatching để vừa map vừa catching.

val usernameResult: Result<Username> = userResult.map {
    checkNotNull(it?.username) { "user is null!" }
}

Một vấn đề đặt ra là làm sao để chain các Result mà phụ thuộc lẫn nhau

// (UserId) -> Result<User?>
suspend fun findUserById(id: UserId): Result<User?> = TODO()

// User -> List<Post>
suspend fun getPostsByUser(user: User): Result<List<Post>> = TODO()

// List<Post> -> Result<Unit>
suspend fun doSomethingWithPosts(posts: List<Post>): Result<Unit> = TODO()

Chúng ta tạo một function flatMap (mapflatten).

// Map and flatten
inline fun <T, R> Result<T>.flatMap(transform: (T) -> Result<R>): Result<R> = mapCatching { transform(it).getOrThrow() }

// or
inline fun <T, R> Result<T>.flatMap(transform: (T) -> Result<R>): Result<R> = map(transform).flatten()
inline fun <T> Result<Result<T>>.flatten(): Result<T> = getOrElse { Result.failure(it) }

Bằng việc sử dụng flatMap, chúng ta có thể chain các Result với nhau

val unitResult: Result<Unit> = findUserById(UserId("#id"))
    .flatMap { user: User? -> runCatching { checkNotNull(user) { "user is null!" } } }
    .flatMap { getPostsByUser(it) }
    .flatMap { doSomethingWithPosts(it) }

Thư viện Arrow-kt cũng cung cấp block result { ... } để có thể handle việc chain các Result với nhau, tránh một số trường hợp sử dụng quá nhiều các nested flatMap. Trong block result { ... }, sử dụng function bind() để lấy giá trị T từ Result<T>. Nếu bind được gọi trên một Result đại diện một lỗi, thì phần code ở phía dưới nó trong block result { ... } sẽ bị bỏ qua (cơ chế short-circuits).

import arrow.core.*

val unitResult: Result<Unit> = result { /*this: ResultEffect*/
    val userNullable: User? = findUserById(UserId("#id")).bind()
    val user: User = checkNotNull(userNullable) { "user is null!" }
    val posts: List<Post> = getPostsByUser(user).bind()
    doSomethingWithPosts(posts).bind()
}

Một số tình huống khác có thể yêu cầu các chiến lược xử lý lỗi phức tạp có thể bao gồm khôi phục hoặc báo cáo lỗi. Ví dụ, chúng ta fetch data từ remote server, nếu bị lỗi thì sẽ lấy data từ cache. Chúng ta có thể sử dụng 2 function recoverrecoverCatching,

class MyData(...)

fun getFromRemote(): MyData = TODO()
fun getFromCache(): MyData = TODO()

val result: Result<MyData> = runCatching { getFromRemote() }
    .recoverCatching { e: Throwable ->
        logger.error(e, "getFromRemote")
        getFromCache()
    }

Sử dụng Result là một cách tiếp cận này tốt hơn, tuy nhiên vấn đề là lỗi luôn luôn là một Throwable, ta phải đọc docs hoặc đọc source code của nó. Một vấn đề nữa là runCatching khi kết hợp với suspend function, nó sẽ catch mọi Throwable, kể cả kotlinx.coroutines.CancellationException, CancellationException là một Exception đặc biệt, được coroutines sử dụng để đảm bảo cơ chế cooperative cancellation (xem issues 1814 Kotlin/kotlinx.coroutines).

Một cách tiếp cận tốt hơn là sử dụng arrow.core.Either, khắc phục các nhược điểm của Result.

Sử dụng arrow.core.Either để xử lý lỗi

Chúng ta có thể sử dụng Either<L, R> như là một type để biểu thị: hoặc là giá trị Left(value: L) , hoặc là giá trị Right(value: R). Nếu theo cách hiểu đơn giản, Either<L, R> = L | R.

public sealed class Either<out A, out B> {
    public data class Left<out A> constructor(val value: A) : Either<A, Nothing>()
    public data class Right<out B> constructor(val value: B) : Either<Nothing, B>()
}

Trong đó, Left thường đại diện cho các giá trị lỗi, giá trị không mong muốn, và Right thường đại diện cho các giá trị thành công, giá trị mong muốn. Nhìn chung Either<L, R> tương tự với Result<T>, Result<T> chỉ tập trung vào type của giá trị thành công mà không quan tâm đến type của giá trị lỗi, và chúng ta có thể xem Result<R> ~= Either<Throwable, R>. Eitherright-biased, tức là các function như map, filter, flatMap, … sẽ theo nhánh Right, nhánh Left bị bỏ qua (được return trực tiếp mà không có hành động nào trên nó cả).

Để tạo ra giá trị Either, ta có thể dụng các function có sẵn như:

  • Left constructor, ví dụ: val e: Either<Int, Nothing> = Left(1)
  • Right constructor, ví dụ: val e: Either<Nothing, Int> = Right(1)
  • left extension function, ví dụ val e: Either<Int, Nothing> = 1.left().
  • right extension function, ví dụ val e: Either<Nothing, Int> = 1.right().
  • Either.catch functions, catch các Exceptions nhưng nó sẽ bỏ qua các fatal Exception
    như kotlinx.coroutines.CancellationException, VirtualMachineError, OutOfMemoryError,…
  • Và nhiều cách được cung cấp bởi arrow.core.Either.Companion.
suspend fun findUserByIdFromDb(id: String): UserDb? = TODO()

fun UserDb.toUser(): User = TODO()

fun Throwable.toUserException(): UserException = TODO()

suspend fun findUserById(id: UserId): Either<UserException, User?> =
    Either
        .catch { findUserByIdFromDb(id.value)?.toUser() } // Either<Throwable, User?>
        .mapLeft { it.toUserException() }                 // Either<UserException, User?>

Chúng ta có thể kiểm tra Either là giá trị Left hay Right, thông qua hai function là isLeft()isLeft(). Either cũng cung cấp hai function tap (tương tự onSucesscủa Result) và tapLeft (tương tự onFailure của Result).

val result: Either<UserException, User?> = findUserById(UserId("#id"))
result.isLeft()
result.isRight()
result.tap { u: User? -> println(u) }
result.tapLeft { e: UserException -> println(e) }

Tương tự như Result, chúng ta sử dụng các function getOrElse, orNull, getOrHandle để lấy giá trị mà Right chứa nếu nó là Right. Một số function hữu ích nữa là fold, bimap, mapError, filter,…

val result: Either<UserException, User?> = findUserById(UserId("#id"))

// Access value
result.getOrElse { defaultValue }
result.orNull()
result.getOrHandle { e: UserException -> defaultValue(e) }

fun handleUser(u: User?) {}
fun handleError(e: UserException) {
    // handle UserException
}

result.fold(
    ifRight = { handleUser(it) },
    ifLeft = { handleError(it) }
)

Tương tự ví dụ khi sử dụng Result, chúng ta cũng muốn chain nhiều giá trị Either với nhau

// (UserId) -> Either<UserException, User?>
suspend fun findUserById(id: UserId): Either<UserException, User?> = TODO()

// User -> Either<UserException, List<Post>>
suspend fun getPostsByUser(user: User): Either<UserException, List<Post>> = TODO()

// List<Post> -> Either<UserException, Unit>
suspend fun doSomethingWithPosts(posts: List<Post>): Either<UserException, Unit> = TODO()

Thư viện Arrow-kt đã cung cấp sẵn function flatMap và block either { ... } để có thể chain các Either với nhau dễ dàng. Trong either { ... } block, sử dụng function bind() để lấy giá trị R từ Either<L, R>. Nếu bind được gọi trên một Either chứa giá trị Left, thì phần code ở phía dưới nó trong block either { ... } sẽ bị bỏ qua (cơ chế short-circuits).

import arrow.core.*

class UserNotFoundException() : UserException("User is null", null)

val result: Either<UserException, Unit> = findUserById(UserId("#id"))
    .flatMap { user: User? ->
        if (user == null) UserNotFoundException().left()
        else user.right()
    }
    .flatMap { getPostsByUser(it) }
    .flatMap { doSomethingWithPosts(it) }

// or either block

val result: Either<UserException, Unit> = either { /*this: EitherEffect*/
    val userNullable: User? = findUserById(UserId("#id")).bind()
    val user: User = ensureNotNull(userNullable) { UserNotFoundException() }
    val posts: List<Post> = getPostsByUser(user).bind()
    doSomethingWithPosts(posts).bind()
}

Cuối cùng là cách khôi phục lỗi. Tương tự như recoverrecoverCatching của Result, chúng ta có thể sử dụng hai function handleErrorhandleErrorWith (giống như flatMap nhưng theo nhánh Left).

class MyData(...)

suspend fun getFromRemote(): MyData = TODO()
suspend fun getFromCache(): MyData = TODO()

val result: Either<Throwable, MyData> =
    Either
        .catch { getFromRemote() }
        .handleErrorWith { e: Throwable ->
            Either.catch {
                logger.error(e, "getFromRemote")
                getFromCache()
            }
        }

Kết luận

Chúng ta đã cùng tìm hiểu Result và sau đó là Either, cả hai type giúp xử lý lỗi và làm giảm side effect. Either còn chỉ rõ về những lỗi có thể xảy ra mà chỉ cần nhìn vào signature của một function. Ngoài ra, Either hỗ trợ cho suspend function mà không làm mất đi cơ chế cancellation, và Arrow-kt cũng có module Fx (https://arrow-kt.io/docs/fx/) giúp cho việc sử dụng Kotlin Coroutines dễ dàng hơn, khi viết các chương trình asyncconcurrent.

Hy vọng bạn thích bài viết này và hôm nay bạn đã học được điều gì đó hữu ích!

Tài liệu tham khảo

  • Arrow-kt – Either docs
  • Arrow-kt – Error handlding
  • Arrow-kt – Monad comprehension
  • Ciocîrlan, D. (2021) Idiomatic error handling in scala, Rock the JVM Blog. Available at: https://blog.rockthejvm.com/idiomatic-error-handling-in-scala/ (Accessed: 04 October 2024).

Author: st-hocnguyen

Related Blog

When Technology Meets a Pioneering Spirit

Our culture

+0

    When Technology Meets a Pioneering Spirit

    SupremeTech’s Hackathon 2025 is not just a coding competition, it’s a place where bold ideas are tested, limits are broken, and new connections are formed. Every team carries its own story – about why they joined, their role in the group, or what they expect to gain after this intense yet inspiring journey. Among them, we had the chance to chat with Quang Dũng, a Technical Leader at SupremeTech, who is participating in a Hackathon for the very first time – while also taking on the role of team leader. Usually busy with deadlines and lines of code, this Hackathon is his opportunity to step away from routine work and challenge himself in a completely different way. We listened to his thoughts on challenges, pioneering spirit, and what he hopes to take away from this event. Let’s join SupremeTech and Quang Dũng in this conversation about the Hackathon! Q&A with Quang Dũng 1. Is this your first time joining a Hackathon?Yes, this is my first Hackathon. Everything feels new: the intensity, the pressure, and how teamwork changes under strict time limits. I wanted to experience this firsthand and compare it with the usual way of running projects. 2. What made you decide to sign up for the event?I wanted to challenge myself – to see how far I can go applying AI in real-world work. This is an opportunity for me and my team to pioneer ways of leveraging AI Copilot in a small group project of 3–5 people, while learning new workflows and testing practical applications. And of course, the prizes are also very attractive – hard to resist! Who knows, maybe I can go home and tell my wife that just two days of Hackathon brought back 50 million VND! (laughs). 3. What is your role in the team? If given the choice, what field would you like your project to be applied to?I’m the team leader – responsible for planning, setting the initial direction, and building a sample demo so everyone has a clear vision. In other words, I set the rhythm so the whole team can work smoothly together. If I had the chance to choose a project topic, I’d want it applied to practical areas like booking/reservation systems or internal management tools such as resource and project management. These fields are highly relevant to business needs and can create immediate value. 4. If you had to describe SupremeTech’s AI Hackathon in three words, what would they be? Challenge – Pioneer – Connection. Challenge: because everyone here must push beyond their own limits.Pioneer: because we are applying the latest AI technologies to real-world problems.Connection: connecting people, ideas, and the future of technology. 5. What do you expect to gain from this Hackathon? In my daily work, I’m often involved in project estimation. I expect this event will help me learn how to integrate AI Copilot into product development workflows. That could make estimations more accurate, save time, and improve overall efficiency at the company. In other words, what I look forward to most is not just the prize, but the knowledge and experience that can truly be applied to work. >> Read related articles: How a Hackathon Changed My Life – A Personal Story from the CEO of SupremeTech Closing SupremeTech’s Hackathon is more than a tech playground, it’s an opportunity for each individual to test themselves, learn, and break past their own limits. Stay tuned for the next tech journeys at SupremeTech, where even the smallest ideas can spark big changes!

    22/08/2025

    112

    Our culture

    +0

      When Technology Meets a Pioneering Spirit

      22/08/2025

      112

      tips when joining AI Hackathon

      Knowledge

      +0

        How Could You Join a Hackathon Without Knowing This?

        In the ever-evolving world of programming, the emergence of intelligent support tools is changing the way we write code. Copilot, often described as “AI-powered Pair Programming”, promises to revolutionize the workflow of software developers. In this article, I’ll focus on GitHub Copilot, the AI tool I personally use every day when coding. What is GitHub Copilot? GitHub Copilot is an AI assistant integrated into IDEs (VS Code, IntelliJ IDEA/PyCharm, Neovim) developed by GitHub and OpenAI. It provides context-aware code suggestions as you type, and includes Copilot Chat for Q&A directly inside the IDE. Key Advantages of GitHub Copilot Faster coding: Reduce time spent on repetitive tasks with context-aware suggestions (functions, code blocks, basic tests).Learn new technologies quickly: Get API/syntax examples directly in your IDE; ask further via Copilot Chat.Automate boring work: Scaffold endpoints, write boilerplate, create sample tests, suggest snippets, and ensure consistent formatting.Seamless IDE integration: Works in VS Code, JetBrains, Neovim; suggestions appear as ghost text/inline as you type. Limitations to Keep in Mind Not always accurate: May generate syntax, logic, or performance errors.Solution: Always review, run lint/tests, and benchmark when needed.Security & copyright risks: Could resemble public code or leak if sensitive data is pasted.Solution: Enable “block suggestions matching public code,” avoid entering secrets, follow organizational policies.Risk of dependency: Over-reliance may weaken fundamental coding skills.Solution: Use Copilot for speed, but keep code reviews and tests.Limited domain knowledge: Suggestions may not fit specific business contexts.Solution: Break down requests, add examples/constraints, manually refine critical parts. Quick Start (VS Code) Install extensions: GitHub Copilot and (optional) GitHub Copilot Chat.Log in to GitHub and enable suggestions in Settings.Create a new file, describe requirements in Vietnamese/English within comments or docstrings.Press Tab to accept, Esc to skip. Check IDE shortcuts for more. Simple Examples Just comment your request, and GitHub Copilot will write code for you. Example 1: Utility function to validate email (JavaScript) // Write function isValidEmail(email: string): boolean function isValidEmail(email) { return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email); } Note: The regex above is basic — adjust according to project needs. Example 2: Quick API skeleton (Node.js/Express) // Create route GET /health that returns { status: 'ok' } app.get('/health', (req, res) => { res.json({ status: 'ok' }); }); Example 3: Basic unit test (Jest) // Write test for sum(a,b): 1+2=3, -1+1=0 test('sum basics', () => { expect(sum(1, 2)).toBe(3); expect(sum(-1, 1)).toBe(0); }); Tips for Using GitHub Copilot Effectively Write clear descriptions/comments: specify input, output, constraints, and examples.Always check & optimize code: run lint/tests, review performance, security.Break down complex requests: guide step by step for more accurate suggestions.Use Copilot Chat to research/explain, but always verify with original docs. Key Notes Enable “block suggestions matching public code” in organizational projects.Avoid pasting secrets (keys, credentials, sensitive data) into prompts. Conclusion GitHub Copilot is an AI assistant that helps you code faster, learn new tech quickly, and automate repetitive tasks — but you still need to review, test, and follow security policies. My Personal Experience Coding with GitHub Copilot Before using GitHub Copilot: Spent lots of time on repetitive, structured code.Slowed down by switching between coding and researching online. When first trying Copilot: Felt efficiency in simple features/functions.Struggled with complex features — Copilot often generated unnecessary code.Spent extra time reviewing Copilot’s output. After long-term use: Significantly reduced time on repetitive tasks (boilerplate, data mapping, simple CRUD, …).More consistent code (naming, structure), better documentation (docs, README) thanks to quick suggestions.Changed workflow: “comment-first” or “test-first” to guide Copilot, using Chat to refine and explain.Formed a risky habit: accepting Copilot’s suggestions too quickly without reviewing. Start Small & Measure Effectiveness Enable Copilot in your IDE, try with a utility function or basic test, turn on the “block public code” filter, and avoid pasting secrets. After one week, measure effectiveness (task completion time, amount of boilerplate written manually, number of minor bugs), then decide how much to apply in projects. Good luck using GitHub Copilot effectively — and may you achieve great success at the Hackathon!

        22/08/2025

        99

        Anh Nguyen T.

        Knowledge

        +0

          How Could You Join a Hackathon Without Knowing This?

          22/08/2025

          99

          Anh Nguyen T.

          CEO of SupremeTech Hackathon

          Our culture

          +0

            How a Hackathon Changed My Life – A Personal Story from the CEO of SupremeTech

            Written by Binh Nguyen, CEO of SupremeTech, from his own Hackathon journey Hi everyone,Today I’d like to share an experience that truly shaped who I am, not just as a professional, but as a builder. This is the story of how a Hackathon transformed my mindset from being just a developer into becoming a tech builder. Before moving into business analysis and later management, I started out as a Front-end Developer. Back in 2016, I was self-learning HTML, CSS, and React.js. Then in 2017, I had the chance to join a Hackathon that completely changed how I approached product development. It was the turning point that made me realize the difference between being a coder and being a builder. Step 1: Accepting the Challenge The Hackathon was organized by the Da Nang Business Incubator in collaboration with the Embassy of Israel, with support from venture capitalists and incubators from Israel. At that time, the prizes weren’t particularly big. But as someone new to the industry, eager to start a career and even dream of building a startup, I felt I had to join. Honestly, another reason was my competitive nature, I wanted to prove that even though I came from a non-tech background and had only been coding for a year, I could still stand shoulder-to-shoulder with others. Step 2: Preparing, Planning and Strategy For those unfamiliar, a Hackathon is a short, intensive coding competition where individuals or teams create a product in just a few days. You start from an idea, build it, and then present it on the last day - the demo day. The products are evaluated by the judges based on the Hackathon’s criteria. Our theme that year was straightforward: turn a startup idea into a product in just two days. If coding is a sport, then Hackathon is like a championship match. To build an app in such a short time, we had to prepare carefully, especially the methods to build a complete app as fast as possible. Boilerplates, bootstrap, pre-made components, even self-made code generation tools to save time… techniques that now are nothing difficult, but the goal of a Hackathon is always the same: build fast and ship immediately! With everything ready, we headed off to the competition. Step 3: Code, Bug, Hack and Chaos After the opening ceremony, we chose our topic and immediately got to work. Our topic was an app to find venues and make group reservations. Like most projects, the first 50% of the codebase went smoothly. We managed to build it within half a day. But the problems started when we moved into the core features and tricky logic like date pickers, slot reservations, venue data, etc. Ideas kept coming, we kept coding, and bugs showed up just as much. By 9 p.m., we were exhausted, buried in bugs, with no clear way forward, and nothing ready for the demo the next morning. So we decided to stop coding and discuss how to deliver on time. After more than an hour of debate, here’s how we planned the remaining work: Stop implementing backend logic and data crawlers, use front-end mockups to ensure demo flowDeploy on Heroku to save time on server setup and have a live productSpend extra time preparing slides to explain during the demo In the end, the chaos turned into something more organized. The funny part was that I, the front-end guy, had to keep coding, while our back-end guy ended up making the slides. Step 4: Demo – The G Hour After polishing things up until the last minute, we got on stage to demo. This part was quite similar to a sprint review, except that we had to stand on stage and present to a big audience. For everyone at ST now, sprint reviews are done very well already, so nothing more to add here. What surprised us was that the judges, especially the Israeli experts, didn’t pay much attention to the detailed features we had spent so much effort on. Instead, they asked “general” questions like: What is the technology that makes this product better than others?In what situations would users actually want to use this product? Step 5: Reflection – What I Gained We didn’t win for two main reasons: The software was quite complete but didn’t clearly show competitive advantages.Vietnamese users at that time didn’t really have the habit of booking tables/services via apps. Although we lost and felt quite upset at that moment, looking back now, I gained so much more: Confidence in setting up the initial codebase and preparing projects for productionUnderstanding that the value of technology lies in efficiency and competitiveness, not the number of featuresRealizing that users don’t care how much effort we put into building an app, they just want their problem solved in the best wayDelivering code fast and continuously, without waitingPreparation and choosing the right topic are the two keys to winningWith the right mindset, I could work with the best developers without losing confidence, even with less knowledge and experience Lastly: Try It Once—It Might Change You Too Hackathon changed me from being just a self-taught coder into a true tech builder. The mindset I gained in those two days has helped me a lot throughout my career until now. I believe many others will also learn valuable lessons from upcoming quality Hackathons. 100 million VND in prizes are waiting for you at the SupremeTech Hackathon this September 2025. Enjoy!

            21/08/2025

            121

            Binh Nguyen T.

            Our culture

            +0

              How a Hackathon Changed My Life – A Personal Story from the CEO of SupremeTech

              21/08/2025

              121

              Binh Nguyen T.

              explore restaurant mobile development

              Online-Merge-Offline Retail

              +0

                Key Considerations When Planning Restaurant Mobile App Development

                In a world where your next customer is more likely to be holding a smartphone than a physical menu, a restaurant mobile app has shifted from a novelty to a necessity. The digital storefront is now as crucial as your physical one. Let's explore how a restaurant mobile app development unfold. While third-party delivery giants once seemed like the only way to play the digital game, savvy restaurant owners are now taking back control, building their own branded experiences to foster loyalty and drive sustainable growth. Why Restaurant Mobile App is Necessary for your Business The way customers interact with restaurants has fundamentally changed. The convenience of browsing, ordering, and paying from a mobile device has created a new standard of expectation. A report from Statista highlighted that: Revenue in the Online Food Delivery market is projected to reach US$1.39tn in 2025. Revenue is expected to show an annual growth rate (CAGR 2025-2030) of 7.64%, resulting in a projected market volume of US$2.02tn by 2030. With a massive portion of that activity happening on mobile devices. Relying solely on third-party platforms like Uber Eats, DoorDash, or GrabFood is a risky strategy. While they offer visibility, they come at a steep price: Crushing Commission Fees: These platforms can charge commissions ranging from 15% to as high as 30% per order, eating directly into your already thin profit margins. Loss of Customer Data: When a customer orders through a third-party app, they are their customer, not yours. You lose access to valuable data about ordering habits, preferences, and contact information, making it impossible to build a direct relationship. Brand Invisibility: On a third-party marketplace, your restaurant is just one logo among a sea of competitors. You have little to no control over the user experience, branding, or how you are presented. A dedicated mobile app flips this dynamic. It becomes your own digital channel, a powerful tool for controlling your brand narrative, cultivating customer loyalty through personalized experiences, and ultimately, growing your sales on your own terms. Define Your Business Goals First Before you you start restaurant mobile app development, you must answer one critical question: What is the primary purpose of this app? A clear objective will guide every decision you make, from features to design to marketing. Don't build an app simply because it's trendy; build it to solve a specific problem for your business and your customers. Consider these common goals for a restaurant app: Streamline Online Ordering & Delivery: This is the most common goal. An app can provide a seamless, branded ordering experience, cutting out the costly middleman and giving you full control over the process from order placement to fulfillment.Boost Customer Loyalty & Retention: An app is the perfect vehicle for a digital loyalty program. You can reward repeat customers with points, exclusive offers, and tiered benefits that keep them coming back. According to the National Restaurant Association’s State of the Restaurant Industry report, 78% of customers say they are more likely to visit a restaurant where they can earn points, even if it isn’t as convenient.Manage Table Reservations or In-Store Pickup: For dine-in establishments, an app that allows customers to book a table in advance can reduce wait times and improve staff efficiency. Similarly, offering a "click-and-collect" option for in-store pickup is a huge convenience for busy customers.Create a Direct Marketing Channel: Push notifications are a game-changer. Imagine being able to send a notification about a "Happy Hour" special on a slow Tuesday afternoon or promote a new menu item directly to your most loyal customers' phones. This direct line of communication is incredibly powerful and cost-effective. Tip: Be clear on what problems the app should solve, don't just build for the sake of trend. A focused app that does one thing exceptionally well is far better than a bloated app that does many things poorly. What Do Your Customers Expect from a Restaurant Mobile App development? Modern diners have high expectations for app usability and convenience. Speed and simplicity are critical in a recent survey 94% of consumers said ‘speed’ of ordering was a top priority. Customers want apps that load quickly, present menus clearly, and let them complete actions with minimal taps.  For example, one-click reordering (where a past order can be placed again instantly) or QR-code “scan to order” menus greatly streamline the process. In short, the user interface (UI) must be fast, intuitive, and mobile-optimized. Other conveniences in restaurant mobile app development keep people coming back. As noted above, loyalty points and exclusive in-app deals are very compelling with 78% of diners favoring restaurants where they can earn rewards. Likewise, personalized recommendations or easy search (by cuisine or dietary preference) can enhance the experience. The app should also feel complete: customers expect to view their order history, saved payment methods, and loyalty status. Integrations like order tracking, real-time table wait estimates, or mobile tipping can further raise satisfaction. Must haves vs. Nice to haves of restaurant mobile app development: At launch, prioritize a smooth core experience: fast menu browsing, simple one-page checkout, and secure payments. Fancy features (AR menu previews, video chat support, etc.) should come later. Listen to early users, if many requests mention split-bill or voice ordering, those can be added in updates. Which Restaurant Mobile App Development Approach Is Right for You? There are three main paths to getting an app: Custom-built app: You hire developers (in-house or outsourced) to build a unique app from scratch. This offers maximum flexibility, true brand uniqueness, and full data ownership. However, it requires more time and up-front budget. You’ll need to manage the development process closely and plan for long-term maintenance.SaaS solution: A pre-built app platform that you brand as your own. Vendors often allow quick launch and lower initial cost, since the core app is already built. You get a customizable look and the basics (menu, ordering, payments) for less. The trade-off is less flexibility, you may be limited to features the vendor supports, and often pay ongoing subscription/licensing fees. Also, you don’t truly own the code or data handling.Third-party marketplace/aggregator: Multi-restaurant ordering platforms (UberEats, DoorDash, Grabfood). This is the quickest way to go live online, but you’ll pay high commissions and lose brand control. Not only that, you are up against many other competitors on the same app. This approach is best only as a supplement, not a replacement for your own branded app. In choosing, you should weigh between costs vs. control. If you go with a white-label app, you're essentially using a pre-built app template provided by a vendor, which you can customize with your own logo, colors, and branding. This option is usually faster and cheaper to launch than building a custom app from scratch. You don’t have to reinvent the wheel because most features like menu browsing, ordering, and payments are already included. But ensure your brand and data remains your own.  A fully custom app costs more but can scale exactly as you need (especially if you plan to add unique features later). Think long-term: owning your app typically costs more upfront but can pay off in loyalty and margins. What to Look For in an Outsourcing Partner If you decide to hire an external development team, choose wisely. Key factors include: Food-industry experience: Look for developers who have worked with restaurants or hospitality clients. They’ll better understand your workflows (menus, kitchens, POS integration) and common restaurant pain points.Strong UI/UX design: The app’s interface should be professional, inviting, and easy to use. Ask for examples of their previous work, ideally in similar industries to ensure they can design a clean, intuitive app.Transparent pricing and timeline: Reputable vendors provide a clear project plan and fixed bid or well-defined hourly estimates. Get milestones and a delivery schedule in writing. Beware of “hidden costs” ensure support/maintenance fees are spelled out.Ongoing support and maintenance: An app is never truly finished. You want a partner who will be available to fix bugs, apply updates (e.g. new OS versions), and help add features over time. Check if they offer post-launch support or a retainer arrangement.Case studies and references: Ask to see case studies or speak with past clients. Successful restaurant app launches or positive reviews from other brands will give confidence. Look for partners who have highlighted measurable results. Choosing a highly-experienced partner like SupremeTech is as important as the idea itself. A knowledgeable team will guide you on architecture (native vs. hybrid app, PWA option), compliance, and industry best practices, helping avoid costly mistakes. Navigating the Challenges of Restaurant App Development Developing a restaurant mobile app for an enterprise chain comes with several common challenges. Awareness early on ensures you can manage risks and build a robust, user‑friendly product. 1. Integration with Existing Systems A major technical hurdle is integrating your app seamlessly with in‑place systems like Point of Sale (POS), inventory tracking, and CRM. Inconsistencies between systems can lead to incorrect menu data, stock errors, delayed order updates, and staff frustration. Solution: Prioritize APIs that connect smoothly with your existing systems and consider cloud-based architecture that ensures real-time synchronization  2. Handling Peak Traffic & Scalability Apps often experience surges during peak meal times or promotional events. Without proper infrastructure, performance will degrade, leading to slow response times or outages.Solution: Use scalable cloud-based servers, load-balancing, and rigorous stress testing under simulated high-traffic conditions. Monitor app performance continuously to prevent downtime during critical periods 3. User Adoption & Promotion Even a well-built app won't succeed without user adoption. Customers may be hesitant to download yet another app or worry about privacy and data entry.Solution: Ensuring Security & Compliance by choosing a trustworthy IT outsourcing company. In addition, developers already know how to adopt best practices early and continuously. Run effective app-store optimization, use in-store signage and QR codes, offer discount, and promote via email, SMS to existing customers. Make the value clear to encourage downloads and use. Summary Table of Key Challenges & Solutions: ChallengeWhy It MattersRecommended SolutionPOS/Inventory IntegrationPrevents order and stock mismatchesStrong APIs, cloud sync, vendor flexibilityPoor UX / Complex InterfaceLeads to low engagement and high abandonmentPrioritize simplicity, early user testingHigh Traffic & Scalability IssuesCauses slow performance or downtime at peak usage- Cloud-based servers- Performance testing- Continuous monitoringData & Payment Security RisksLiability for breaches and brand damage- Secure protocols- Encryption- Compliance standardsToo Much Customization ComplexityOverwhelms users and complicates kitchen workflows- Limit options- Guide user choices- Use recommendation logicDevice & Platform TestingInconsistent performance across OS versions and devicesTest on emulators and real devices frequentlyLow User AdoptionApp fails to reach critical mass of users- Incentives- Clear value messaging- In-store promotionPartner Expertise & ReliabilityDevelopment delays, misalignment, or hidden costs- Review case studies- Ask technical questions- Confirm ongoing supportMarket Saturation / DifferentiationHard to attract customers in a sea of existing apps- Add branded loyalty- Personalization- Integrated featuresOngoing Maintenance NeedsApps become outdated quickly without consistent updatesPlan support contracts and phased feature rollouts Lessons Learned from Unsuccessful Restaurant Apps 1. Ando: David Chang’s Delivery‑Only Restaurant App Ando was created in 2016 by famous chef David Chang (founder of Momofuku) as a delivery‑only restaurant brand in New York City, accepting orders via its own mobile app and website, with delivery handled by UberRUSH. It gained significant attention and raised about $7 million in funding. Despite the buzz, by early 2018 Ando was acquired by Uber Eats and shut down as a standalone brand. Why Ando Failed: Limited scale and delivery-only model: Operating only in limited zones without physical dining locations made it hard to build broad customer loyalty.Low user adoption beyond early adopters: Despite backing from a high‑profile chef and investors, the app did not achieve mainstream traction.No long-term differentiation or experience: Without sit-down experience or a broader brand ecosystem, its novelty faded quickly.Acquisition rather than growth: Uber Eats acquired and absorbed Ando, effectively ending its app as a separate entity suggesting it underperformed as a standalone digital brand. Lesson: A novel concept and strong branding don’t guarantee long-term success. Without sufficient distribution, differentiation, and repeated customer experience, app-only formats can struggle to scale. 2. GarfieldEats: A “Entertainment + Ordering” Themed App GarfieldEats launched in 2018 as a Garfield-themed ghost kitchen and delivery app across cities like Toronto, Dubai, and London. Through its own branded app, customers could order Garfield‑branded food and even play games or watch Garfield cartoon episodes while ordering. Despite the creativity, the brand shut down by late 2020, ceasing both the restaurant and app operation. Why GarfieldEats Failed: Overly complex concept: Combining dining with gaming and branding added novelty but diluted focus on core food quality and ordering reliability.Poor economics and unprofitability: High overhead licensing costs, themed decor, app maintenance without sufficient volume undermined margins.Pandemic pressures and rent disputes: COVID‑19 shutdowns and financial issues like unpaid rent forced closures across locations.Low repeat usage or engagement: The app experience was more gimmick than utility; customers did not return regularly. Lesson: Entertainment value and branding alone can’t sustain an app-driven dining concept. If food, app reliability, or repeat value are weak, novelty quickly wears off. Build Smart, Grow Long-Term Industry reports and expert analyses were used throughout for example, recent restaurant surveys show 75% of orders are now off-premises, top chains drive 60% of sales from repeat app users, and third-party delivery platforms charge roughly 15–30% fees. These trends underline why owning your app and engaging customers directly is now essential.  A great restaurant app is an investment, not a cost so you must start by choosing the IT partner wisely. When planned and executed well, it pays dividends in customer loyalty, data, and higher-margin sales. Remember that choosing the right development partner is as important as the app’s idea. Start with a clear vision and minimum viable features, then launch based on real customer use. Over time, the app will become a cornerstone of your brand’s experience. 📩 Read more articles about us here: https://www.supremetech.vn/blog/  ☎️Contact us to see how we can support your loyalty app strategy.

                06/08/2025

                185

                Online-Merge-Offline Retail

                +0

                  Key Considerations When Planning Restaurant Mobile App Development

                  06/08/2025

                  185

                  Customize software background

                  Want to customize a software for your business?

                  Meet with us! Schedule a meeting with us!