Distrito Telefónica. Hub de Innovación y Talento

Volver
Development

Primeros pasos con Kotlin Multiplatform para Android e iOS

Kotlin

Kotlin

Herramientas

Kotlin Multiplataform es una tecnología que nos ayuda a desarrollar proyectos multiplataforma. Permite ejecutar el mismo código en diferentes sistemas operativos como Android, iOS, macOs, Windows, etc. 

Nos vamos a centrar en las aplicaciones móviles, donde Kotlin Multiplatform Mobile es utilizada cada vez por más desarrolladores. Sin embargo, hay muchos desarrolladores que han oído hablar de ella, pero no saben cómo empezar. 

Por lo tanto, el objetivo de este artículo es darte algunos consejos útiles para ayudarte a entender qué tipo de tecnología es y finalmente desarrollar tu primer proyecto KMM. 

Estructura del proyecto KMM

Como puedes imaginar estas son las herramientas que necesitas para construir un proyecto KMM para móviles Android e iOS. 

  • Android Studio 
  • XCode 
  • Java 
  • Kotlin Multiplatform Mobile Plugin 
Una vez que hayas instalado todas las herramientas, podrás iniciar tu primer proyecto KMM. Pero antes de empezar hay que entender qué es KMM y cómo funciona. 

Estructura del proyecto KMM

El objetivo de la tecnología Kotlin Multiplatform Mobile es desarrollar aplicaciones con una lógica común para las plataformas Android e iOS. Por ello, KMM cuenta con una estructura determinada para lograrlo:
Vista de la estructura del proyecto KMM en Android Studio

Vista de la estructura del proyecto KMM en Android Studio

Cada proyecto KMM incluye tres módulos: 

Módulo compartido

Es un módulo de Kotlin que contiene la lógica común para aplicaciones Android e iOS. Compila en una biblioteca Android y un marco de trabajo iOS y utiliza el sistema de construcción Gradle con el plugin Kotlin Multiplatform aplicado y tiene objetivos para Android e iOS. Puedes configurar el módulo a través de build.gradle que encontrarás en su carpeta. 

Como he comentado, el módulo compartido contiene el código que es común para las aplicaciones Android e iOS. En cualquier caso, es posible que necesites versiones de código específicas para cada plataforma para implementar la misma lógica en ambas. 

Como veremos más adelante, tenemos el mecanismo expect/actual (donde la clase expect es como una interfaz y las clases actual son su implementación) para gestionar estas situaciones. Organizaremos el código fuente del módulo compartido en tres conjuntos de fuentes

  • commonMain: donde se encuentra el código tanto entre Android e iOS. Aquí se incluyen las declaraciones de clases comunes, llamadas declaraciones expect. 
  • androidMain: donde se encuentra la parte específica de Android. Aquí se adapta el comportamiento de la clase expect a Android y se denomina implementaciones reales. 
  • iosMain: donde se encuentra la parte específica de iOS. Aquí se adapta el comportamiento de la clase expect a Android y se denomina implementaciones reales. 

módulo androidApp

Es un módulo de Kotlin que se integra en una aplicación Android. Puedes acceder a build.gradle en la carpeta androidApp donde puedes configurar tu aplicación Android como de costumbre. 

módulo iOSApp

Se trata de un proyecto Xcode que se convierte en una aplicación iOS. Xcode utiliza su propio sistema de compilación. Como resultado, la aplicación iOS no utiliza Gradle para conectarse con las otras partes del proyecto. En su lugar, utiliza el módulo compartido como un artefacto externo - marco. Cuando creas la aplicación KMM en AndroidStudio tienes que decidir si quieres incluir la parte iOS como marco de trabajo Regular o como gestor de dependencias CocoaPods. En este artículo lo utilizaremos como marco habitual. 
Estructura del proyecto KMM

Estructura del proyecto KMM

Dependencias

Existen dos tipos de dependencias en los proyectos Kotlin Multiplatform Mobile. 

Por un lado, Dependencias multiplataforma son bibliotecas multiplataforma, que han sido desarrolladas para ser compatibles con ambas plataformas. Estas dependencias se añaden al conjunto de fuentes commonMain. 

Por otro lado, las Dependencias nativas se gestionan con el gestor específico de la plataforma: cocoaPods o Gradle. 

Además, habrá algunas situaciones en las que necesitarás añadir dependencias multiplataforma que hayan sido desarrolladas para una plataforma específica para Android o específica para iOS, a pesar de que hayan sido desarrolladas para Kotlin Multiplataforma. Estas dependencias se añaden al conjunto de fuentes androidMain y iosMain respectivamente en gradle config del módulo compartido. Veremos un ejemplo más adelante. 

Tal vez, te estés preguntando si es posible añadir bibliotecas de plataformas específicas a Kotlin Multiplataform. La respuesta es sí, pero no es muy recomendable. Por ejemplo, Kotlin admite dependencias Objective-C y dependencias Swift si sus API se exportan a Objective-C. Las dependencias Swift puras todavía no son compatibles. 

Como resultado, la situación más habitual es utilizar Dependencias multiplataforma para desarrollar el código compartido y Dependencias nativas para desarrollar el código específico de la plataforma dentro del módulo AndroidApp e iOSApp. 

Tu primer proyecto KMM

En este punto, ya entiendes lo que es KMM y cómo está estructurado, por lo que estás preparado para iniciar tu primer proyecto KMM. 

En primer lugar, abre Android Studio y selecciona crear una aplicación Kotlin Multiplataform. Y en la siguiente pantalla, selecciona distribución de marco de trabajo IOS a marco de trabajo regular. 
Pasos para crear un proyecto de KMM en Android Studio

Pasos para crear un proyecto de KMM en Android Studio

Ahora, si ejecutas iOsApp y AndroidApp verás algo así: 
Primera ejecución en iOS y Android

Primera ejecución en iOS y Android

Si abres el archivo Greeting.kt ubicado en shared/src/commonMain/kotlin verás por qué ambas aplicaciones están mostrando este mensaje. Esta clase se encuentra en commonMain, por lo que es código común para Android e iOS. En este caso, el código es el mismo pero el mensaje es diferente porque cada aplicación muestra su versión operativa del sistema. 

Además, puedes abrir el archivo MainActivity.kt ubicado en el módulo androidApp y el archivo ContentView.swift ubicado en el módulo iOSApp . Después, verás que estamos mostrando el texto resultante de Greeting.greet() . Si intentas modificar esta función en la clase Greeting.kt, el mensaje cambiará en ambas aplicaciones. 

El mensaje contiene la versión del sistema operativo de cada plataforma. Así, si abres el archivo Platform.kt ubicado en el conjunto de fuentes commonMain verás la declaración de clase de expect

Y su implementación se encuentra en los archivos Platform.kt, ubicados en los conjuntos de fuentes androidMain y iosMain respectivamente, donde desarrollamos las clases reales para proporcionar comportamiento a la interfaz. De este modo, podemos imprimir la versión SO de ambas plataformas. 
interface Platform { 
    val name: String 
} 
 
expect fun getPlatform(): Platform 
class AndroidPlatform : Platform { 
    override val name: String = "Android ${android.os.Build.VERSION.SDK_INT}" 
} 
 
actual fun getPlatform(): Platform = AndroidPlatform()class IOSPlatform: Platform { 
    override val name: String = UIDevice.currentDevice.systemName() + " " + UIDevice.currentDevice.systemVersion 
}

actual fun getPlatform(): Platform = IOSPlatform()
Ahora, vamos a añadir una lógica más compleja a la aplicación. Para ello, vamos a añadir algunas bibliotecas multiplataforma. 

En primer lugar, vamos a mostrar la fecha actual. Así que vamos a añadir la biblioteca kotlinx-datetime . Esta biblioteca es compatible con la multiplataforma, por lo que la añadiremos al conjunto de fuentes commonMain: 

sourceSets { 
    val commonMain by getting { 
        dependencies { 
            implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0") 
        } 
    } 
   ... 
  } 

A continuación, creamos un nuevo archivo en el módulo compartido: shared/src/commonMain/kotlin/Date.kt: 

import kotlinx.datetime.* 
 
fun getToday(): String { 
    val today = Clock.System.todayIn(TimeZone.currentSystemDefault()) 
    return today.dayOfMonth.toString() + " / " + today.monthNumber.toString() + " / " + today.year.toString() 
} 

Por último, modificamos la función greet() para que muestre la fecha actual: 

fun greet(): String { 
        return "Hello, ${platform.name}!" + "\n\n" + "Today is ${getToday()}" 
    } 
Ejecución de iOS y Android mostrando la fecha actual

Ejecución de iOS y Android mostrando la fecha actual

A continuación, vamos a añadir una solicitud de API para mostrar la respuesta. Para ello, vamos a añadir algunas dependencias. Todas ellas se utilizarán para la lógica común, por lo que se añadirán a build.gradle en el módulo compartido. 

Sin embargo, tendremos que añadir algunas dependencias multiplataforma a commonMain y algunas dependencias multiplataforma a los conjuntos de fuentes androidMain e iosMain porque son dependencias específicas de la plataforma. 

Para realizar la solicitud a la API vamos a añadir estas dependencias: kotlinx.coroutines, kotlinx.serialization y Ktor. Y tenemos que añadir la plataforma específica Ktor Client a cada conjunto de fuentes. 
val ktorVersion = "2.2.4" 
 
sourceSets { 
    val commonMain by getting { 
        dependencies { 
            implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0") 
            implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4") 
            implementation("io.ktor:ktor-client-core:$ktorVersion") 
            implementation("io.ktor:ktor-client-content-negotiation:$ktorVersion") 
            implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion") 
        } 
    } 
    val androidMain by getting { 
        dependencies { 
            implementation("io.ktor:ktor-client-android:$ktorVersion") 
        } 
    } 
    val iosMain by creating { 
        dependencies { 
            implementation("io.ktor:ktor-client-darwin:$ktorVersion") 
        } 
        ... 
    } 
    ... 
} 

A continuación, vamos a añadir la solicitud de API. En primer lugar, definiremos nuestro modelo: 

import kotlinx.serialization.SerialName 
import kotlinx.serialization.Serializable 
 
@Serializable 
data class RMCharacterList ( 
    @SerialName("results") 
    val characters: List<RMCharacter>, 
) 
@Serializable 
data class RMCharacter( 
    val name: String, 
    val species: String, 
) 

En segundo lugar, añadiremos la llamada de solicitud API a nuestra lógica común en Greeting.kt. 

import io.ktor.client.HttpClient 
import io.ktor.client.call.body 
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation 
import io.ktor.client.request.get 
import io.ktor.serialization.kotlinx.json.json 
import kotlinx.serialization.json.Json 
 
class Greeting { 
    private val platform: Platform = getPlatform() 
    private val httpClient = HttpClient { 
        install(ContentNegotiation) { 
            json(Json { 
                prettyPrint = true 
                isLenient = true 
                ignoreUnknownKeys = true 
            }) 
        } 
    } 
    @Throws(Exception::class) 
    suspend fun greet(): String { 
        val response: RMCharacterList = 
            httpClient.get("https://rickandmortyapi.com/api/character").body() 
        val oneRMCharacter = response.characters.firstOrNull() 
        return "Hello, ${platform.name}!" + 
                "\n\n" + 
                "Today is ${getToday()}" + 
                "\n\n" + 
                "First character is ${oneRMCharacter?.name} and is ${oneRMCharacter?.species}" 
    } 
} 


Por último, tenemos que añadir el permiso de acceso a Internet en el Manifest.xml y actualizar nuestra Android App e iOS App para mostrar la respuesta de la API. 

Vamos a modificar MainActivity.kt para actualizar Android App. 

import androidx.compose.runtime.* 
 
class MainActivity : ComponentActivity() { 
    override fun onCreate(savedInstanceState: Bundle?) { 
        super.onCreate(savedInstanceState) 
        setContent { 
            MyApplicationTheme { 
                Surface( 
                    modifier = Modifier.fillMaxSize(), 
                    color = MaterialTheme.colors.background 
                ) { 
                    var text by remember { mutableStateOf("Loading") } 
                    LaunchedEffect(true) { 
                        text = try { 
                            Greeting().greet() 
                        } catch (e: Exception) { 
                            e.localizedMessage ?: "error" 
                        } 
                    } 
                    GreetingView(text) 
                } 
            } 
        } 
    } 
} 

 vamos a modificar iOSApp.swift y ContentView.swift para actualizar iOS App. 

import SwiftUI 
 
@main 
struct iOSApp: App { 
var body: some Scene { 
  WindowGroup { 
   ContentView(viewModel: ContentView.ViewModel()) 
  } 
} 
}import SwiftUI 
import shared 
 
struct ContentView: View { 
    @ObservedObject private(set) var viewModel: ViewModel 
    var body: some View { 
        Text(viewModel.text) 
    } 
} 
extension ContentView { 
    class ViewModel: ObservableObject { 
        @Published var text = "Loading..." 
        init() { 
            // Data will be loaded here 
            Greeting().greet { greeting, error in 
                        DispatchQueue.main.async { 
                            if let greeting = greeting { 
                                self.text = greeting 
                            } else { 
                                self.text = error?.localizedDescription ?? "error" 
                            } 
                        } 
                    } 
        } 
    } 
} 

Si ejecutamos ambos proyectos veremos este resultado. 
Resultado

Resultado

En resumen, este post demuestra cómo utilizar Android Studio para crear una aplicación móvil para iOS y Android utilizando Kotlin Multiplatform Mobile. Aquí, has descubierto qué es Kotlin Multiplataform para móviles, has entendido cómo está estructurada y has empezado tu primer proyecto KMM. 

Concretamente, en este proyecto se ha añadido cierta lógica común en el módulo compartido para construir un saludo con código específico de la plataforma en el módulo compartido. Y luego has añadido código específico de la plataforma para implementar la interfaz de usuario (IU) en ambas plataformas: Android e iOS. 

Espero que este post te ayude a empezar con KMM como lo hizo conmigo. Hay algunos cambios de última hora que he intentado explicar lo mejor que he sabido, pero desde mi sincera opinión, no son tan complicados. 

Además, si eres desarrollador de Android, como ves, el cambio no es muy importante. Por otro lado, si eres desarrollador de iOS, los cambios de última hora son mayores. Sin embargo Kotlin y Swift son lenguajes muy similares, por lo que estarás preparado para desarrollar un proyecto KMM muy pronto, poco a poco. 

Ahora, estás preparado para actualizar tu proyecto KMM añadiendo algunas funciones nuevas. ¡Buena suerte!