Code Challenge: Number of Islands in Kotlin

The Number of Islands problem is a common interview question that involves counting the number of islands in a 2D grid. Each island is made up of connected pieces of land (denoted as '1') surrounded by water (denoted as '0'). The challenge is to count how many separate islands exist in the grid, where an island is formed by horizontally or vertically adjacent lands.



We will discuss multiple ways to solve this problem, explaining their pros and cons. Let's dive into solving this problem using Kotlin.


Problem Definition

Given a 2D binary grid grid, return the number of islands. An island is surrounded by water and is formed by connecting adjacent lands horizontally or vertically. The grid is surrounded by water on all sides.

Example 1:

Input:

[
  ["1", "1", "1", "1", "0"],
  ["1", "1", "0", "1", "0"],
  ["1", "1", "0", "0", "0"],
  ["0", "0", "0", "0", "0"]
]

Output:

1

Example 2:

Input:

[
  ["1", "1", "0", "0", "0"],
  ["1", "1", "0", "0", "0"],
  ["0", "0", "1", "0", "0"],
  ["0", "0", "0", "1", "1"]
]

Output:

3

Approach 1: Depth-First Search (DFS)

The most intuitive approach is to use Depth-First Search (DFS). We start from each land cell ('1'), mark it as visited (or change it to water '0'), and recursively check its adjacent cells (up, down, left, right). Every time we find an unvisited land cell, we count it as a new island.

Algorithm:

  1. Traverse the grid.
  2. If we find a '1', increment the island count and use DFS to mark the entire island as visited.
  3. For each DFS, recursively mark the neighboring land cells.

Kotlin Implementation:

fun numIslands(grid: Array<CharArray>): Int {
    if (grid.isEmpty()) return 0
    var count = 0

    // Define DFS function
    fun dfs(grid: Array<CharArray>, i: Int, j: Int) {
        // Return if out of bounds or at water
        if (i < 0 || i >= grid.size || j < 0 || j >= grid[0].size || grid[i][j] == '0') return
        // Mark the land as visited
        grid[i][j] = '0'
        // Visit all 4 adjacent cells
        dfs(grid, i + 1, j) // down
        dfs(grid, i - 1, j) // up
        dfs(grid, i, j + 1) // right
        dfs(grid, i, j - 1) // left
    }

    // Iterate over the grid
    for (i in grid.indices) {
        for (j in grid[i].indices) {
            if (grid[i][j] == '1') {
                // Found a new island
                count++
                dfs(grid, i, j)
            }
        }
    }
    return count
}

Time Complexity:

  • O(m * n), where m is the number of rows and n is the number of columns. Each cell is visited once.

Space Complexity:

  • O(m * n) in the worst case (if the entire grid is land), as we may need to store all cells in the call stack due to recursion.

Approach 2: Breadth-First Search (BFS)

We can also use Breadth-First Search (BFS). Instead of using recursion like in DFS, we use a queue to explore all adjacent cells iteratively. The process is similar, but the main difference lies in the order of exploration.

Algorithm:

  1. Start from an unvisited land cell ('1').
  2. Use a queue to explore all adjacent land cells and mark them as visited.
  3. Each BFS initiation represents a new island.

Kotlin Implementation:

fun numIslands(grid: Array<CharArray>): Int {
    if (grid.isEmpty()) return 0
    var count = 0
    val directions = arrayOf(intArrayOf(0, 1), intArrayOf(1, 0), intArrayOf(0, -1), intArrayOf(-1, 0))

    fun bfs(i: Int, j: Int) {
        val queue: LinkedList<Pair<Int, Int>>= LinkedList()
        queue.offer(Pair(i, j))
        grid[i][j] = '0' // Mark the starting cell as visited

        while (queue.isNotEmpty()) {
            val (x, y) = queue.poll()
            for (dir in directions) {
                val newX = x + dir[0]
                val newY = y + dir[1]
                if (newX in grid.indices && newY in grid[0].indices && grid[newX][newY] == '1') {
                    grid[newX][newY] = '0' // Mark as visited
                    queue.offer(Pair(newX, newY))
                }
            }
        }
    }

    for (i in grid.indices) {
        for (j in grid[i].indices) {
            if (grid[i][j] == '1') {
                count++
                bfs(i, j)
            }
        }
    }
    return count
}

Time Complexity:

  • O(m * n), where m is the number of rows and n is the number of columns. Each cell is visited once.

Space Complexity:

  • O(m * n), which is required for the queue in the worst case.

Approach 3: Union-Find (Disjoint Set)

The Union-Find (or Disjoint Set) approach is another efficient way to solve this problem. The idea is to treat each land cell as an individual component and then union adjacent land cells. Once all unions are complete, the number of islands is simply the number of disjoint sets.

Algorithm:

  1. Initialize each land cell as a separate island.
  2. For each neighboring land cell, perform a union operation.
  3. The number of islands will be the number of disjoint sets.

Kotlin Implementation:

class UnionFind(private val m: Int, private val n: Int) {
    private val parent = IntArray(m * n) { it }

    fun find(x: Int): Int {
        if (parent[x] != x) parent[x] = find(parent[x]) // Path compression
        return parent[x]
    }

    fun union(x: Int, y: Int) {
        val rootX = find(x)
        val rootY = find(y)
        if (rootX != rootY) parent[rootX] = rootY
    }

    fun getCount(): Int {
        return parent.count { it == it }
    }
}

fun numIslands(grid: Array<CharArray>): Int {
    if (grid.isEmpty()) return 0
    val m = grid.size
    val n = grid[0].size
    val uf = UnionFind(m, n)

    for (i in grid.indices) {
        for (j in grid[i].indices) {
            if (grid[i][j] == '1') {
                val index = i * n + j
                // Try to union with adjacent cells
                if (i + 1 &lt; m &amp;&amp; grid[i + 1][j] == '1') uf.union(index, (i + 1) * n + j)
                if (j + 1 &lt; n &amp;&amp; grid[i][j + 1] == '1') uf.union(index, i * n + (j + 1))
            }
        }
    }
    val islands = mutableSetOf&lt;Int&gt;()
    for (i in grid.indices) {
        for (j in grid[i].indices) {
            if (grid[i][j] == '1') {
                islands.add(uf.find(i * n + j))
            }
        }
    }
    return islands.size
}

Time Complexity:

  • O(m * n), as we perform a union operation for each adjacent land cell.

Space Complexity:

  • O(m * n) for the union-find parent array.

Calling in main():

fun main() {
    val grid1 = arrayOf(
        charArrayOf('1', '1', '1', '1', '0'),
        charArrayOf('1', '1', '0', '1', '0'),
        charArrayOf('1', '1', '0', '0', '0'),
        charArrayOf('0', '0', '0', '0', '0')
    )
    println("Number of Islands : ${numIslands(grid1)}")  // Output: 1
    
    val grid2 = arrayOf(
        charArrayOf('1', '1', '0', '0', '0'),
        charArrayOf('1', '1', '0', '0', '0'),
        charArrayOf('0', '0', '1', '0', '0'),
        charArrayOf('0', '0', '0', '1', '1')
    )
    println("Number of Islands : ${numIslands(grid2)}")  // Output: 3
}



Which Solution is Best?

  1. DFS/BFS (Approaches 1 & 2): These are the simplest and most intuitive solutions. Both have a time complexity of O(m * n), which is optimal for this problem. DFS uses recursion, which might run into issues with large grids due to stack overflow, but BFS avoids this problem by using an iterative approach. If you want simplicity and reliability, BFS is preferred.

  2. Union-Find (Approach 3): This approach is more advanced and has a similar time complexity of O(m * n). However, it can be more difficult to understand and implement. It also performs well with path compression and union by rank, but for this problem, the DFS/BFS approach is usually sufficient and easier to implement.

Conclusion

For this problem, BFS is the recommended solution due to its iterative nature, which avoids recursion issues with large grids, while still being efficient and easy to understand.


Full Problem description in LeetCode


Thank you for reading my latest article! I would greatly appreciate your feedback to improve my future posts. 💬 Was the information clear and valuable? Are there any areas you think could be improved? Please share your thoughts in the comments or reach out directly. Your insights are highly valued. 👇😊.  Happy coding! 💻✨

America’s Best-Managed Companies of 2024: Insights and Analysis

In 2024, America's Best-Managed Companies list offers a comprehensive evaluation of organizations based on key performance metrics that extend beyond financial success. This annual ranking is published by The Drucker Institute of Claremont Graduate University and uses a well-balanced methodology to highlight companies excelling in diverse domains, ensuring both operational effectiveness and societal impact.

                                                            Source: visualcapitalist


How Are Companies Ranked?

The methodology behind the ranking emphasizes a balanced approach by assigning weights to five categories:

  1. Social Responsibility (24%) - Commitment to environmental, social, and governance (ESG) principles.
  2. Customer Satisfaction (22%) - Ensuring superior customer experience and loyalty.
  3. Innovation (21%) - The ability to generate new ideas and solutions.
  4. Employee Engagement and Development (20%) - Fostering a productive and growing workforce.
  5. Financial Strength (13%) - Stability and financial growth.

These weighted categories result in an effectiveness score (T-score) ranging from 0 to 100, where 50 equals the average.


Top 5 Companies Leading the List

1. Apple (Effectiveness Score: 80.6)

Apple continues to set benchmarks with an outstanding focus on social responsibility (72) and customer satisfaction (85). Known for innovative products like the iPhone and Apple Watch, the company also excels in fostering strong customer loyalty and a sustainable business approach.

2. NVIDIA (Effectiveness Score: 79.8)

NVIDIA shines in innovation (97), showcasing its dominance in AI and GPU technologies. The company has been a frontrunner in driving technological advancements, helping industries transition to the future of computing.

3. Microsoft (Effectiveness Score: 78.4)

With strong scores across social responsibility (58) and employee engagement (88), Microsoft remains a leader in both societal contributions and workforce development. The company’s focus on cloud services, AI, and sustainability cements its position as a global tech powerhouse.

4. Intel (Effectiveness Score: 78.3)

Intel’s strength lies in innovation (87) and financial robustness (86). As a key player in the semiconductor industry, the company is pivotal in driving advancements in computing technology.

5. Tesla (Effectiveness Score: 77.5)

Tesla's dominance in innovation (93) solidifies its status as a leader in electric vehicles and renewable energy. While excelling in disruptive technologies, the company also focuses on improving sustainability practices.


Key Takeaways from the Rankings

  1. Tech Companies Dominate the Top 10 Out of the top 10, five are technology giants, including Apple, NVIDIA, Microsoft, Alphabet, and Adobe. Their combined focus on innovation and customer satisfaction highlights the growing importance of tech-driven solutions.

  2. Balance Between Financial Success and Social Responsibility While financial performance is an important factor, companies are increasingly judged on their societal impact and contribution to broader global challenges like climate change, employee well-being, and ethical practices.

  3. Diverse Industry Representation Apart from tech companies, industries like consumer goods (Procter & Gamble), automotive (Tesla), and financial services (Visa) have secured positions, proving that effective management is not limited to any specific sector.

  4. Innovation as a Differentiator Companies scoring high in innovation, such as NVIDIA and Tesla, are redefining industry standards and shaping the future of business and society.


Key Insights on Specific Categories

Social Responsibility

Adobe leads this category with a score of 72, reflecting its focus on sustainability, equity, and inclusion. Companies with high social responsibility scores are likely investing in impactful ESG initiatives that resonate with stakeholders.

Customer Satisfaction

Apple achieves the highest score of 85, a testament to its unparalleled customer loyalty and product ecosystem. Customer-centric strategies remain vital for sustaining market leadership.

Innovation

NVIDIA scores an impressive 97, highlighting its role as a transformative force in AI, gaming, and data science. Companies excelling here often define the future of their respective industries.

Employee Engagement and Development

Microsoft and Walmart score high in this category, emphasizing the importance of workforce engagement and talent development for organizational success.

Financial Strength

Tesla and Intel dominate this metric, showcasing their financial stability and ability to fund innovative projects while maintaining operational efficiency.


Notable Companies to Watch

  1. Adobe (9th Place, Score: 73.9) Adobe’s efforts in innovation and social responsibility make it a strong contender, especially in the creative and marketing software space.

  2. PepsiCo (16th Place, Score: 71.5) PepsiCo's inclusion highlights the relevance of consumer goods companies in implementing innovative practices and sustainable business models.

  3. Amazon (18th Place, Score: 71.2) While maintaining a strong financial position and innovation capabilities, Amazon faces challenges in social responsibility and employee engagement.


Summary

The 2024 list of America's Best-Managed Companies showcases the evolving landscape of corporate management. Companies are no longer judged solely on financial metrics but also on their ability to innovate, contribute to societal well-being, and create inclusive work environments. As industries adapt to global challenges, this balanced evaluation serves as a blueprint for sustainable success.

Whether you're an investor, employee, or customer, understanding these rankings can provide valuable insights into the companies shaping the future of business and society.


Sources and References

Efficient Background Work in Android Kotlin: Boost Performance and User Experience

In Android development, background work refers to tasks that are executed outside of the main UI thread. These tasks can include network requests, database operations, file uploads, or even periodic updates that don’t require immediate user interaction. Running such operations on the main thread can lead to poor user experience, UI freezes, or even crashes. That’s why background work is essential for keeping the app responsive and functional. 🚀

Types of Background Work in Android Kotlin

Android provides various options to manage background work efficiently, and choosing the right approach is crucial for the app’s performance and battery life. Let’s explore the most common types of background work in Android Kotlin:

1. AsyncTask (Deprecated in API Level 30)

AsyncTask was one of the earliest ways to perform background work in Android. It allows background operations to be executed and provides a mechanism to update the UI thread once the task completes. However, it’s now deprecated due to its limitations in handling larger tasks and thread management.

Example:

val task = object : AsyncTask<Void, Void, String>() {
    override fun doInBackground(vararg params: Void?): String {
        return "Task completed!"
    }

    override fun onPostExecute(result: String?) {
        super.onPostExecute(result)
        textView.text = result
    }
}
task.execute()

⚠️ Why Avoid It? AsyncTask is less efficient for complex or long-running tasks and often leads to memory leaks or UI thread blocking. Android now recommends other solutions.

2. Handler & HandlerThread

A Handler and HandlerThread are used to manage background threads by allowing communication between the UI thread and a background thread. HandlerThread is a specialized thread that has a Looper and can handle background tasks on a separate thread.

Example:

val handlerThread = HandlerThread("BackgroundThread")
handlerThread.start()
val handler = Handler(handlerThread.looper)
handler.post {
    // Perform background task
}

This approach is useful for tasks that require multiple executions on a background thread.

3. WorkManager (Recommended)

WorkManager is the recommended solution for managing background work, particularly for tasks that require guaranteed execution (even if the app is terminated) or need to run periodically. It's part of Android Jetpack, and it abstracts the complexities of scheduling background tasks and handles them across all Android versions.

WorkManager supports tasks like:

  • One-time tasks (e.g., sending data to the server)
  • Periodic tasks (e.g., syncing data every hour)
  • Tasks with constraints (e.g., only when the device is charging or connected to Wi-Fi)

Example:

val workRequest = OneTimeWorkRequestBuilder<MyWorker>()
    .setConstraints(
        Constraints.Builder()
            .setRequiredNetworkType(NetworkType.CONNECTED)
            .build()
    )
    .build()

WorkManager.getInstance(context).enqueue(workRequest)

Here, MyWorker is a class where the background work is implemented. With WorkManager, you don’t have to worry about managing threads directly, as it handles background execution under various conditions.

4. Coroutines & Kotlin Flow

Coroutines offer a modern way to handle background work in Android. By using launch or async builders in Kotlin, developers can perform background tasks asynchronously without blocking the UI thread. Kotlin’s Flow is perfect for tasks that emit continuous data, such as streaming network data or database queries.

Example using Coroutines:

GlobalScope.launch(Dispatchers.IO) {
    // Perform background work here
    val result = fetchDataFromNetwork()
    withContext(Dispatchers.Main) {
        // Update the UI with result
        textView.text = result
    }
}

Example using Flow:

fun getData(): Flow<String> = flow {
    emit("Fetching data...")
    delay(1000) // Simulating network delay
    emit("Data fetched!")
}

Using coroutines and Flow simplifies background work by making it easier to handle asynchronous operations and responses.

Achieving Efficient Background Work in Android Kotlin

Efficient background work ensures that the app runs smoothly and doesn't drain resources or consume unnecessary battery life. Here are some best practices for achieving efficient background work in Android Kotlin:

1. Use WorkManager for Guaranteed Execution

WorkManager is the most efficient way to handle tasks that need guaranteed execution. It allows you to schedule tasks with constraints (e.g., only run when the device is charging or connected to Wi-Fi). WorkManager takes care of device-specific limitations, so it’s the best option for long-running tasks. 🔋

2. Opt for Coroutines Over Threads

Coroutines are lightweight and more efficient than traditional threads. By using Dispatchers.IO or Dispatchers.Default, you can offload background tasks without blocking the main thread. This reduces the risk of UI freezes and improves performance. 🏎️

3. Use Kotlin Flow for Continuous Background Data

For tasks that involve continuous data streams (like network responses), Flow is the ideal choice. It allows you to manage the data asynchronously and ensures smooth updates to the UI.

4. Batch Tasks When Possible

Instead of performing individual network requests or background tasks one at a time, try to batch them together. For example, if you need to sync data, group it into one task that runs periodically, rather than making multiple individual requests. This reduces overhead and makes the app more efficient. 📦

5. Use Constraints in WorkManager

To further optimize background tasks, you can use constraints in WorkManager. For example, only execute the task when the device is connected to a Wi-Fi network or during specific times of the day to reduce unnecessary usage of resources. 🌐

Benefits and Importance of Efficient Background Work in Android Kotlin

Switching to modern background work techniques like WorkManager, Coroutines, and Flow offers several benefits over traditional methods:

  1. Improved App Performance: Using background work properly ensures that the UI remains responsive, and heavy tasks don't block the main thread. 🚀
  2. Battery Efficiency: Efficient background work, particularly through WorkManager, helps conserve battery life by executing tasks only under specific conditions, like when the device is charging or connected to Wi-Fi. 🔋
  3. Ease of Maintenance: Modern approaches like Kotlin Coroutines and WorkManager simplify code and make it more maintainable, reducing the complexity of managing threads manually. 🛠️
  4. Better User Experience: By performing heavy tasks in the background and updating the UI with smooth transitions, the app feels faster and more fluid. 🎮
  5. Reliability: With guaranteed task execution in WorkManager, even if the app is killed or the device reboots, tasks can still complete successfully. 📅

Summary

Efficient background work is a key component of creating high-performance Android applications. By using modern approaches like WorkManager, Coroutines, and Kotlin Flow, developers can ensure that their apps are more responsive, reliable, and power-efficient. For Android developers new to background work, these tools provide an easy and efficient way to manage tasks asynchronously without overcomplicating the process.

If you want to boost your app’s performance and create a seamless experience for your users, adopting these modern background work techniques is a must! 🌟


Feel free to explore these concepts and apply them in your own Android projects!

Thanks for reading! 🎉 I'd love to know what you think about the article. Did it resonate with you? 💭 Any suggestions for improvement? I’m always open to hearing your feedback to make my posts even better! 👇🚀. Happy coding! 💻✨

Displaying a Custom List Using MVVM Architecture with Jetpack Compose in Android Kotlin

 In Android development, organizing your code in a structured way is essential for scalability and maintainability. One of the most popular architectural patterns for Android apps is MVVM (Model-View-ViewModel). MVVM helps you separate concerns and enhances testability, making your code more manageable. In this post, we’ll learn how to display a custom list using MVVM architecture with Jetpack Compose, Kotlin, and Coroutines.

What We Will Build

We will create an Android app using Jetpack Compose to display a custom list of data (for example, a list of users or items). The app will use MVVM to separate the UI, data handling, and business logic. The app will also use Kotlin Coroutines for asynchronous operations like fetching data from a network or a local database.

Prerequisites

  • Basic knowledge of Jetpack Compose, MVVM architecture, and Kotlin.
  • Android Studio installed with Kotlin support.

Steps to Build the Custom List App with MVVM and Coroutines

Let’s break this down into the following steps:

  1. Create the Data Model: Define the data you want to display in the list.
  2. Create the Repository: Handle data fetching, either from a network or a local database.
  3. Create the ViewModel: Expose the data to the UI and manage UI-related data.
  4. Create the Composables: Use Jetpack Compose to create the UI that observes the ViewModel.

1. Create the Data Model

The data model represents the data structure that will be displayed in the list. In this case, we will define a simple User data model.

data class User(val id: Int, val name: String, val email: String)

This model will be used to represent individual items in the list.


2. Create the Repository

In the MVVM architecture, the Repository is responsible for managing data and fetching it from different sources (e.g., network, local database). Here’s an example of a simple repository using a suspended function (asynchronous operation) to simulate fetching data from a remote API.

import com.example.test.data.User
import kotlinx.coroutines.delay

class UserRepository {
    // Simulate fetching data asynchronously
    suspend fun getUsers(): List<User> {
        // Simulating network delay using a delay function
        delay(2000) // Simulating a network call delay
        return listOf(
            User(1, "John Doe", "johndoe@example.com"),
            User(2, "Jane Smith", "janesmith@example.com"),
            User(3, "Alex Johnson", "alexjohnson@example.com"),
            User(4, "Mark john", "markjohn@example.com"),
            User(5, "Bill laste", "billlaste@example.com"),
            User(6, "Deep lucifer", "deeplucifer@example.com"),
            User(7, "Kora Frank", "korafrank@example.com"),
            User(8, "Atticus Austin", "atticusaustin@example.com"),
            User(9, "Eve Reese", "evereese@example.com"),
            User(10, "Scarlet Frost", "scarletfrost@example.com"),
            User(11, "Nyla Martin", "nylamartin@example.com"),
            User(12, "Tony Moran", "tonymoran@example.com"),
            User(13, "Rudy Escobar", "rudyescobar@example.com"),
            User(14, "Waverly Clay", "waverlyclay@example.com"),
            User(15, "Zev Velez", "zevvelez@example.com")
        )
    }
}

This repository will return a list of User objects after a simulated network delay.


3. Create the ViewModel

The ViewModel is responsible for handling the UI-related data and business logic. It acts as a bridge between the Repository and the UI. The ViewModel will call the UserRepository to fetch the list of users asynchronously using Kotlin Coroutines.

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.test.data.User
import com.example.test.respository.UserRepository
import kotlinx.coroutines.launch

class UserViewModel : ViewModel() {
    private val userRepository = UserRepository()

    private val _users = MutableLiveData<List<User>>()
    val users: LiveData<List<User>> get() = _users

    fun fetchUsers() {
        viewModelScope.launch {
            val userList = userRepository.getUsers()
            _users.postValue(userList)
        }
    }
}

In the UserViewModel:

  • We use viewModelScope.launch to launch a coroutine in the ViewModel’s scope, ensuring that the coroutine will be cancelled when the ViewModel is cleared.
  • We fetch the data asynchronously from the repository and post the data to the LiveData object (_users), which is then observed by the UI.

4. Create the Composables

Now, let’s use Jetpack Compose to create the UI. We will display the list of users in a LazyColumn, which is the Compose equivalent of a RecyclerView. The UI will observe the LiveData exposed by the ViewModel.

Main Screen Composable

import androidx.lifecycle.viewmodel.compose.viewModel

@Composable
fun UserListScreen(userViewModel: UserViewModel = viewModel()) {
    // Observe the users LiveData from ViewModel
    val users by userViewModel.users.observeAsState(emptyList())

    // Fetch users when the composable is first displayed
    LaunchedEffect(Unit) {
        userViewModel.fetchUsers()
    }

    // Display the list of users
    LazyColumn(modifier = Modifier.fillMaxSize()) {
        items(users) { user ->
            UserItem(user)
        }
    }
}

@Composable
fun UserItem(user: User) {
    val context = LocalContext.current
    Card(
        modifier = Modifier
            .fillMaxWidth()
            .padding(16.dp)
            .clickable {
                Toast.makeText(context, user.name, Toast.LENGTH_SHORT).show()
            },
        elevation = CardDefaults.cardElevation(4.dp),
        shape = RoundedCornerShape(8.dp)
    ) {
        Column(
            modifier = Modifier.padding(16.dp)
        ) {
            Text(text = user.name, style = MaterialTheme.typography.headlineMedium)
            Text(text = "Email: ${user.email}", style = MaterialTheme.typography.bodyMedium)
        }
    }
}

Key Points:

  • We use observeAsState to observe changes to the users LiveData.
  • The LazyColumn is used to display the list of users in a scrollable view. It’s efficient and only renders the items that are visible on screen, similar to RecyclerView.
  • Each UserItem is displayed in a Card with padding and elevation.

Putting It All Together

In your MainActivity, you can set the UserListScreen composable to display the data.

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            TestTheme {
                // Provide UserViewModel using the viewModel() function
                UserListScreen()
            }
        }
    }
}

Now, when the app runs, it will fetch the list of users asynchronously using Kotlin Coroutines and display the list in Jetpack Compose.



Conclusion

By following the steps above, we successfully built an app that displays a custom list of users using MVVM architecture, Jetpack Compose, and Kotlin Coroutines.

This pattern is not only clean and testable but also provides a seamless separation of concerns between the UI, business logic, and data management. Using LiveData, viewModelScope, and Jetpack Compose ensures a reactive UI that updates automatically when data changes, all while maintaining good performance and keeping the codebase manageable.

With Jetpack Compose's declarative approach, building UIs becomes easier and more intuitive, and combining it with MVVM and Coroutines ensures that your app remains scalable and maintainable.

I appreciate you taking the time to read my latest post! 🙏 I’m always looking to improve, so your feedback would be incredibly helpful. What did you think of the content? Was there anything that could be better explained? Let me know in the comments! 👇🌟”. Happy coding! 💻✨

Integrating GraphQL in Android with Kotlin and Jetpack Compose

GraphQL is gaining popularity as a modern API design approach, offering flexibility and efficiency compared to traditional REST APIs. In this blog post, we’ll explore how to integrate GraphQL in Android using Kotlin and Jetpack Compose. We’ll demonstrate fetching data from a GraphQL API and displaying it on the UI, evaluate alternatives to GraphQL, and look at recent projects or companies leveraging GraphQL.


Why GraphQL?

GraphQL, developed by Facebook in 2012 and open-sourced in 2015, is a query language for APIs that allows clients to request exactly the data they need. Unlike REST, GraphQL consolidates data fetching into a single endpoint and avoids over-fetching or under-fetching data. Key benefits include:

  • Flexible Queries: Fetch only the fields required.

  • Single Endpoint: Simplifies API design.

  • Strong Typing: Errors are easier to catch with a well-defined schema.

  • Real-Time Data: Supports subscriptions for live updates.


Setting Up GraphQL in Android with Kotlin

To integrate GraphQL in an Android app, we’ll use Apollo GraphQL, a powerful GraphQL client for Kotlin Multiplatform.


Step 1: Add Dependencies

Add the Apollo library to your build.gradle file:

plugins {
    id("com.apollographql.apollo3") version "3.9.0"
}

dependencies {
    implementation("com.apollographql.apollo3:apollo-runtime:3.9.0")
}

apollo {
    packageName.set("com.example.graphql")
}


Step 2: Create a GraphQL Schema

Save your GraphQL queries in the src/main/graphql/com/example/graphql directory. For instance:

query GetCharacter {
    characters {
        results {
            id
            name
            status
        }
    }
}

Apollo will generate Kotlin classes for the query automatically.


Step 3: Initialize Apollo Client

Set up the Apollo Client in your application:

import com.apollographql.apollo3.ApolloClient

val apolloClient = ApolloClient.Builder()
    .serverUrl("https://rickandmortyapi.com/graphql")
    .build()


Fetching Data from GraphQL and Displaying with Compose

Let’s fetch data using the generated GetCharacterQuery class and display it using Jetpack Compose.

Fetching Data

import com.example.graphql.GetCharacterQuery
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.runBlocking

fun fetchCharacters(): Flow<List<GetCharacterQuery.Result?>> = flow {
    val response = apolloClient.query(GetCharacterQuery()).execute()
    emit(response.data?.characters?.results ?: emptyList())
}

Displaying Data in Compose

import androidx.compose.foundation.layout.*
import androidx.compose.foundation.text.BasicText
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.launch

@Composable
fun CharacterListScreen() {
    var characters by remember { mutableStateOf<List<GetCharacterQuery.Result?>>(emptyList()) }
    val scope = rememberCoroutineScope()

    LaunchedEffect(Unit) {
        scope.launch {
            fetchCharacters().collect { characters = it }
        }
    }

    Column(modifier = Modifier.padding(16.dp)) {
        characters.forEach { character ->
            character?.let {
                BasicText(text = "Name: ${it.name}, Status: ${it.status}")
                Spacer(modifier = Modifier.height(8.dp))
            }
        }
    }
}

@Preview
@Composable
fun PreviewCharacterListScreen() {
    CharacterListScreen()
}


Alternatives to GraphQL

While GraphQL offers unique advantages, other options may suit specific use cases better:

REST APIs

  • Strengths: Simplicity, widespread adoption, caching support via HTTP.

  • Limitations: Over-fetching or under-fetching data.

gRPC

  • Strengths: Efficient binary serialization, bi-directional streaming, and great performance.

  • Limitations: Less flexible for clients, harder to debug.

Firebase Realtime Database

  • Strengths: Real-time synchronization for mobile apps.

  • Limitations: Limited query flexibility compared to GraphQL.

Which One to Choose?

  • Choose GraphQL when you need flexible data fetching, strong typing, or real-time capabilities.

  • Choose REST for simpler, traditional APIs.

  • Choose gRPC for high-performance communication between microservices.

Real-World Examples

  • GraphQL: Facebook, GitHub, Shopify, Netflix.
  • REST API: Twitter, Reddit, most public-facing APIs.
  • gRPC: Google Cloud services, Kubernetes, Uber.


Companies and Projects Using GraphQL

  1. Facebook: Originally created GraphQL for its mobile apps to improve performance.

  2. GitHub: Provides a public GraphQL API for developers.

  3. Shopify: Uses GraphQL for efficient e-commerce data fetching.

  4. Netflix: Leverages GraphQL for flexible data retrieval across multiple platforms.

  5. Airbnb: Implemented GraphQL to accelerate development and deliver richer user experiences.



Summary

GraphQL combined with Kotlin and Jetpack Compose provides a powerful toolkit for building modern Android applications. It simplifies data fetching, reduces boilerplate code, and enhances app performance. As we’ve seen, integrating GraphQL in an Android app is straightforward and offers significant flexibility compared to traditional APIs.

Try integrating GraphQL into your next Android project and experience the benefits firsthand!


More information about GraphQL: Apollo GraphQL , Github GraphQL API, HyGraph, Facebook Graph API 

📢 Feedback: Did you find this article helpful? Let me know your thoughts or suggestions for improvements! 😊 please leave a comment below. I’d love to hear from you! 👇
Happy coding! 💻✨

Understanding Parent and Child Coroutine Relationships in Kotlin

 Coroutines in Kotlin are designed to support structured concurrency, ensuring that tasks launched within a scope adhere to a predictable lifecycle. When working with coroutines, it is essential to understand the relationship between parent and child coroutines, especially in scenarios involving exceptions, cancellations, and scope management. In this article, we'll explore these relationships in detail with examples.

Parent and Child Coroutines

In Kotlin, coroutines launched using builders like launch or async within a coroutine scope automatically form a parent-child relationship. This relationship governs how exceptions and cancellations propagate between coroutines.

Key Characteristics of Parent-Child Coroutines

  1. Cancellation Propagation:

    • If the parent coroutine is cancelled, all its child coroutines are also cancelled.

    • If a child coroutine fails (throws an exception), the parent coroutine is cancelled by default unless a special construct like SupervisorJob is used.

  2. Structured Concurrency:

    • Parent coroutines do not complete until all their child coroutines have completed. This ensures a predictable execution flow.

  3. Error Propagation:

    • Exceptions thrown in a child coroutine propagate to the parent, which can handle the exception or let it crash the application.

Example: Parent Cancels Child

Here's an example demonstrating how cancellation propagates from parent to child:

import kotlinx.coroutines.*

fun main() = runBlocking {
    val parentJob = launch {
        val childJob = launch {
            repeat(10) { i ->
                println("Child is working: $i")
                delay(500)
            }
        }

        delay(1200) // Allow child to run for a while
        println("Parent is cancelling")
        childJob.cancelAndJoin() // Cancels the child job
    }

    parentJob.join()
    println("Parent completed")
}

Output:

Child is working: 0
Child is working: 1
Parent is cancelling
Parent completed

In this example, the parent coroutine explicitly cancels its child, ensuring proper resource cleanup.


Exceptions in Child Coroutines

When a child coroutine throws an unhandled exception, it propagates to the parent. By default, this cancels the parent and any sibling coroutines.

Example: Child Throws Exception

import kotlinx.coroutines.*

fun main() = runBlocking {
    val parentJob = launch {
        launch {
            throw IllegalArgumentException("Child coroutine failed!")
        }
        launch {
            repeat(5) {
                println("Sibling is working")
                delay(300)
            }
        }
    }

    parentJob.join()
    println("Parent completed")
}

Output:

Exception in thread "main" java.lang.IllegalArgumentException: Child coroutine failed!

The exception in one child causes the parent to cancel, which in turn cancels its sibling.


Handling Exceptions with SupervisorJob

Using a SupervisorJob allows exceptions in a child coroutine to not affect siblings or the parent coroutine.

Example: Isolating Failures with SupervisorScope

import kotlinx.coroutines.*

fun main() = runBlocking {
    val parentJob = supervisorScope {
        launch {
            throw IllegalArgumentException("Child coroutine failed!")
        }
        launch {
            repeat(5) {
                println("Sibling is working")
                delay(300)
            }
        }
    }

    println("Parent completed")
}

Output:

Sibling is working
Sibling is working
Sibling is working
Sibling is working
Sibling is working
Parent completed

In this example, the failure of one child does not affect its sibling or the parent.


CoroutineExceptionHandler

A CoroutineExceptionHandler provides a centralized way to handle uncaught exceptions in a coroutine scope.

Example: Using CoroutineExceptionHandler

import kotlinx.coroutines.*

fun main() = runBlocking {
    val exceptionHandler = CoroutineExceptionHandler { _, exception -&gt;
        println("Caught exception: ${exception.message}")
    }

    val parentJob = launch(exceptionHandler) {
        launch {
            throw IllegalArgumentException("Child coroutine failed!")
        }
    }

    parentJob.join()
    println("Parent completed")
}

Output:

Caught exception: Child coroutine failed!
Parent completed

The CoroutineExceptionHandler prevents the application from crashing and gracefully logs the exception.


Summary

  • Parent and child coroutines form a structured hierarchy where cancellation and exceptions propagate by default.

  • The use of SupervisorJob or SupervisorScope isolates failures, ensuring one child’s failure does not cancel its siblings.

  • A CoroutineExceptionHandler allows centralized exception handling to gracefully manage errors.

By understanding these concepts, you can design robust, maintainable, and predictable coroutine-based applications in Kotlin.

📢 Feedback: Did you find this article helpful? Let me know your thoughts or suggestions for improvements! 😊 please leave a comment below. I’d love to hear from you! 👇

Happy coding! 💻✨


Gemini 2.0: The Next Evolution in AI by Google

Google's Gemini 2.0 represents a significant leap forward in AI technology, offering enhanced capabilities, performance, and flexibility for developers, businesses, and organizations worldwide. As the next chapter in Google's generative AI models, Gemini 2.0 brings new possibilities for creating smarter, more efficient solutions across various industries.

What is Gemini 2.0?

Gemini 2.0 is Google's most advanced AI model, designed to push the boundaries of what generative AI can achieve. With advancements in natural language processing, multimodal capabilities, and fine-tuned customization, it allows developers to build applications that can generate, process, and understand complex data with greater accuracy and efficiency.

Key Features of Gemini 2.0

  1. Multimodal Capabilities: One of the standout features of Gemini 2.0 is its ability to handle multiple types of inputs, such as text, images, and even video, making it highly versatile for various applications. This multimodal capability ensures that developers can create richer and more dynamic user experiences, particularly in fields like marketing, customer service, and creative content creation.

  2. Enhanced Natural Language Understanding: Gemini 2.0 has made significant strides in understanding and generating human-like text. Whether it's generating code, composing articles, or answering questions, Gemini 2.0 excels in delivering contextually relevant and coherent responses.

  3. Fine-Tuned Customization: The model offers enhanced customization features, enabling developers to tailor it for specific use cases. By using the Gemini API, developers can fine-tune the model to meet the needs of different industries, including healthcare, finance, entertainment, and more.

  4. Scalability and Reliability: As part of the Vertex AI platform, Gemini 2.0 offers scalability, ensuring that businesses can rely on it for large-scale deployments. Whether you're building AI-driven applications or running complex AI models, Gemini 2.0 delivers the reliability and performance that developers need to build innovative solutions.

  5. Integration with Google Cloud: Gemini 2.0 seamlessly integrates with Google Cloud services, providing developers with access to powerful tools like BigQuery, Google Kubernetes Engine, and more. This integration allows for the creation of AI-powered applications with minimal setup and maximum performance.

Developer-Friendly Tools and APIs

Google has made it easier than ever for developers to interact with Gemini 2.0. Through the Gemini API, developers can integrate the AI model into their applications with just a few lines of code. The API supports a variety of use cases, from simple chatbots to more complex generative tasks like code generation and multimedia processing.

Where to Access Gemini 2.0?

Developers can start exploring Gemini 2.0 through:

Currently, Gemini 2.0 is in an experimental phase, with general availability expected early next year.

Use Cases for Gemini 2.0

Gemini 2.0 opens up new opportunities in several industries, such as:

  • Customer Support: Build intelligent chatbots that can handle a wide range of customer queries in a natural, human-like manner.
  • Creative Industries: Automate content creation, including articles, blogs, and even videos, saving time and resources for content creators.
  • Healthcare: Use Gemini 2.0 for medical research, patient data analysis, and generating insights to assist in decision-making.
  • Finance: Analyze financial data and generate reports or forecasts with higher accuracy, helping businesses make data-driven decisions.

Why Choose Gemini 2.0?

Google's Gemini 2.0 is a game-changer for developers seeking to integrate AI into their applications. With its advanced capabilities, flexibility, and ease of use, it offers a powerful solution for creating intelligent, scalable applications. Whether you're a startup or a large enterprise, Gemini 2.0 empowers you to unlock new possibilities and drive innovation in your field.

For more information about Gemini 2.0 and how you can get started, visit the Gemini 2.0 page.


Feedback Request:

What do you think of Gemini 2.0? Do you have any thoughts or feedback on its potential applications? Feel free to share your insights in the comments!