Blog Infos
Author
Published
Topics
, , , ,
Author
Published

While I was working on my previous article about Mastering Date and Time Management in iOS with Kotlinx DateTime: A Step-by-Step Guide, I made a surprising discovery: adjusting the visibility modifier of my functions had a much larger impact than I ever anticipated. Specifically, marking certain functions as internal dramatically reduced the size of the platform-specific libraries generated by my project. Before this, I had never really considered how much of a difference visibility modifiers could make. In this blog post, I’ll share how you can use the “internal” modifier to optimize your KMP project by minimizing dependencies and keeping your build size under control.

Photo by Paul Skorupskas on Unsplash

 

What Does “Internal” Mean in Kotlin?

In Kotlin, the internal visibility modifier means that a function, class, or property is only accessible within the module in which it is defined. This means that when you mark a function as internal, it is not accessible to other modules or libraries outside your own.

1. Limit the Spread of Dependencies

When you mark a function that relies on an external library as internal, you ensure that this dependency is not available to other modules. This prevents other modules from becoming unnecessarily heavy by accessing functions and libraries they don’t actually need.

Example: Suppose you’re dealing with date-time operations in your KMP project. Instead of making functions that handle conversions between platform-specific and shared date-time formats public, you can mark them as internal. This ensures that only the internal implementation has access to the external date-time library, without making other modules dependent on it.

// Common code (shared module)
import kotlinx.datetime.Instant
import kotlin.time.Duration.Companion.days

expect class PlatformDateTime

internal expect fun PlatformDateTime.fromPlatform(): Instant

internal expect fun Instant.toPlatform(): PlatformDateTime

fun doSomething(date: PlatformDateTime): PlatformDateTime {
    return date
        .fromPlatform()
        .plus(10.days)
        .toPlatform()
}

In this example, the fromPlatform and toPlatform functions are marked as internal, limiting their visibility to within the module. This prevents other modules from accessing these functions and becoming dependent on the kotlinx.datetime library used for date-time conversions.

Impact: Without using internal, the generated XCFramework header file was 547 lines long. By making all the DateTime-related functions internal, those details are hidden from the outside, and the header file is reduced to just 154 lines—a reduction of more than 3.5 times. This not only reduces the size but also keeps the API surface clean and focused.

2. Protect the Abstraction of Your Implementation

Another reason to make functions internal is to protect the abstraction of your implementation. When you make a function public, you expose the implementation to the rest of your codebase, which can lead to unwanted dependencies and an increased risk of breaking changes in the future.

Tip: Only make functions public that are absolutely necessary for other modules to interact with, and keep the rest of the implementation internal.

internal expect fun PlatformDateTime.fromPlatform(): Instant

internal expect fun Instant.toPlatform(): PlatformDateTime

Here, marking the functions as internal ensures that the implementation details remain hidden, allowing you to modify or update the underlying date-time logic without affecting other parts of your project.

3. Reduce the Risk of Dependency Conflicts

By keeping dependencies internal, you prevent potential conflicts where different modules require different versions of the same dependency. This can reduce the complexity of dependency management and make your project more stable.

Scenario: Imagine you have both an iOS and an Android module in your KMP project, and both use different date-time libraries. By making the functions dependent on these libraries internal, you minimize the risk of conflicts if you ever need to update the library versions.

Job Offers

Job Offers

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

No results found.

Jobs

4. Reduce the Size of Your Build Output

Limiting the visibility of functions can reduce the size of your compiled output. This is because internal functions and their associated dependencies are not exported to other modules, resulting in a smaller binary.

# iOS directory size using internal
XCFrameworks/release:
16.560.327 bytes (16,6 MB on disk)

# iOS directory size without using internal
XCFrameworks/release:
17.630.737 bytes (17,7 MB on disk)

Impact: The size of the generated platform-specific library before using internal was significantly larger compared to after marking relevant functions as internal. The difference is clear when looking at the final build sizes.

5. Simplify Maintenance of Your Codebase

An additional benefit of using internal is that it simplifies the maintenance of your codebase. You can make changes to internal functions without worrying about the impact on other modules. This makes it easier to update or replace dependencies without causing widespread issues in your project.

Conclusion

Using the internal visibility modifier in Kotlin Multiplatform projects is an effective way to limit the scope of dependencies and keep your project manageable. By restricting the visibility of functions that depend on external libraries, you ensure a leaner, more efficient codebase with fewer dependency conflicts. It’s a small adjustment that brings significant benefits to the performance and scalability of your project.

This article is previously published on proandroiddev.com

YOU MAY BE INTERESTED IN

YOU MAY BE INTERESTED IN

blog
It’s one of the common UX across apps to provide swipe to dismiss so…
READ MORE
blog
Hi, today I come to you with a quick tip on how to update…
READ MORE
blog
Automation is a key point of Software Testing once it make possible to reproduce…
READ MORE
blog
Drag and Drop reordering in Recyclerview can be achieved with ItemTouchHelper (checkout implementation reference).…
READ MORE
Menu