Showing posts with label Android Kotlin Tutorials. Show all posts
Showing posts with label Android Kotlin Tutorials. Show all posts

Exploring Scope Functions in Kotlin Compose

Kotlin provides several powerful scope functions that make your code more readable and concise. These scope functions — let, run, apply, also, and with — are particularly useful when working with Kotlin's modern Android development framework, Jetpack Compose. 

Scope functions in Kotlin allow you to execute a block of code within the context of an object. Each of these functions has different characteristics and return values. They can be used for various purposes, like modifying an object, chaining function calls, or simplifying code readability.

1. let – Transform the Object and Return the Result

The let function executes a block of code on the object it is invoked on and returns the result of the block. It's often used when working with an object and returning a result without modifying the original object.

Syntax:

val result = object.let {
    // Do something with object
    "result"
}

Example in Compose: Suppose we want to handle an event in Jetpack Compose like a button click, where we only need to transform the result or pass it on to another function:

val buttonText = "Click me"
val result = buttonText.let {
    it.toUpperCase()
}

Text(text = result)  // Output: "CLICK ME"

In this example, the let function transforms buttonText to uppercase without modifying the original string.

2. run – Execute a Block and Return the Result

run is similar to let, but it is typically used when you want to execute a block of code and return a result. Unlike let, run doesn’t take the object as an argument — instead, it works within the object's context.

Syntax:

val result = object.run {
    // Do something with object
    "result"
}

Example in Compose: When creating a composable, you can use run to set up complex UI elements or operations:

val result = "Hello".run {
    val length = length
    "Length of text: $length"
}

Text(text = result)  // Output: "Length of text: 5"

Here, we used run to access properties and perform operations on a string. The function directly returns the result without modifying the object.

3. apply – Configure an Object and Return It

The apply function is used when you want to modify an object and return the modified object itself. It’s particularly useful for setting multiple properties on an object.

Syntax:

val modifiedObject = object.apply {
    // Modify object properties
}

Example in Compose: For instance, in Jetpack Compose, you can use apply when configuring a Modifier object to add multiple modifications:

val modifier = Modifier
    .padding(16.dp)
    .apply {
        background(Color.Blue)
        fillMaxSize()
    }

Box(modifier = modifier) {
    Text(text = "Hello World")
}

In this case, the apply function allows chaining multiple properties on the Modifier object and returns the same object after applying changes.

4. also – Perform an Action and Return Object

The also function is often used when you want to perform additional actions on an object but don’t want to change or return a new object. It’s often used for logging or debugging.

Syntax:

val objectWithAction = object.also {
    // Perform actions like logging
}

Example in Compose: Suppose you want to log a value when a user clicks a button in Compose:

val clickCount = remember { mutableStateOf(0) }

Button(onClick = {
    clickCount.value = clickCount.value.also {
        println("Button clicked: ${clickCount.value} times")
    } + 1
}) {
    Text("Click Me")
}

In this example, the also function is used to log the click count before updating the value of clickCount.

5. with – Execute a Block on an Object Without Returning It

The with function is used when you want to perform several actions on an object without modifying or returning it. It operates similarly to run, but unlike run, which operates in the context of the object, with requires the object to be passed explicitly.

Syntax:

with(object) {
    // Perform actions
}

Example in Compose: If you want to configure a composable’s properties, you can use with for better readability:

val modifier = with(Modifier) {
    padding(16.dp)
    background(Color.Green)
    fillMaxSize()
}

Box(modifier = modifier) {
    Text("Welcome to Compose!")
}

Here, with is used to apply multiple modifiers without repeatedly referencing the Modifier object.

Key Differences Between Scope Functions

  • let: Useful when you transform the object and return a result. The object is passed as an argument to the block.

  • run: This is similar to let, but the object is accessed directly within the block. It is helpful when returning a result after performing operations.

  • apply: Modifies the object and returns the object itself. Ideal for object configuration.

  • also: Similar to apply, but used primarily for performing side actions (like logging), while returning the original object.

  • with: Works like run, but requires the object to be passed explicitly and is used when you need to operate on an object without modifying it.

When to Use Each in Jetpack Compose

  • let: When you must transform or pass an object’s value.

  • run: When performing operations within the context of an object and returning the result.

  • apply: When you need to modify an object (like a Modifier) and return it after changes.

  • also: For performing additional actions (e.g., logging or debugging) without changing the object.

  • with: When you want to execute multiple operations on an object without modifying it.

Summary

Scope functions are essential to Kotlin’s functional programming style, offering a concise and readable way to work with objects. In Jetpack Compose, they help streamline UI development, manage states, and enhance overall code readability. Whether you’re configuring UI elements, performing transformations, or logging actions, scope functions can significantly reduce boilerplate and improve the efficiency of your Kotlin Compose code.

📢 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! 💻✨

Debounce Operator in Kotlin

When developing Android applications, especially ones that involve user interaction, it’s common to deal with situations where rapid user input or system events trigger multiple updates. This can lead to unnecessary computations, network calls, or UI updates, which affect performance and degrade the user experience.

To handle this issue effectively, Kotlin Flow provides a powerful operator known as debounce. This operator allows you to prevent unnecessary emissions by ensuring that a flow only emits a value if there’s a specified delay without any further emissions. In this article, we’ll explore how the debounce operator works and how to leverage it in Android development using Kotlin Coroutines.


What is the debounce Operator?

The debounce operator ensures that only the last value is emitted after a certain amount of idle time. If a flow emits values continuously within a short period, the operator will delay the emission until the flow has stopped emitting for a predefined duration.

This is particularly useful in scenarios like:

  • Search functionality: When a user types a search query, you want to wait until the user has stopped typing for a certain period before making an API call.
  • Text field input: Preventing multiple rapid updates to the UI or server requests while a user types.
  • Event handling: When multiple events are emitted within a short duration (e.g., button clicks), the debounce operator can limit the number of events handled.

How Does debounce Work?

Let’s break down how the debounce operator works:

  1. Value Emission: The flow emits values over time.
  2. Idle Period: When a new value is emitted, the timer is reset.
  3. Delay Period: The flow will wait for the specified time before emitting the latest value.
  4. Only Last Value: If another value is emitted during the idle period, the previous value will be discarded, and the timer resets.

This ensures that only the last emitted value after a specified delay is considered.


Syntax of debounce

The syntax for using the debounce operator in Kotlin Flow is simple:

flow.debounce(timeoutMillis)
  • timeoutMillis: The time (in milliseconds) to wait for new emissions before emitting the most recent value.

Example: Implementing to Implement in an Android Search Feature

Let’s look at an example of how the debounce operator can be used to implement search functionality in an Android app.

Step 1: Setting Up the Search Flow

Imagine we have a search bar where the user types text, and we want to fetch results from the server after the user stops typing for a brief period. Here’s how you can use debounce in your ViewModel.

ViewModel Code:

class SearchViewModel : ViewModel() {

    private val _searchQuery = MutableStateFlow("")
    val searchResults: StateFlow<List<String>> get() = _searchQuery
        .debounce(500)  // Wait for 500ms of idle time before emitting
        .flatMapLatest { query ->
            // Simulate a network request
            fetchSearchResults(query)
        }
        .stateIn(viewModelScope, SharingStarted.Lazily, emptyList())

    // Simulating a network call or repository interaction
    private fun fetchSearchResults(query: String): Flow<List<String>> = flow {
        // Simulating network delay
        delay(1000)
        // Returning mock data
        emit(listOf("Result 1", "Result 2", "Result 3"))
    }

    fun onSearchQueryChanged(query: String) {
        _searchQuery.value = query
    }
}

Step 2: Observing in the UI (Activity or Fragment)

In the Activity or Fragment, you would collect the searchResults state and update the UI based on the search results.

class SearchFragment : Fragment(R.layout.fragment_search) {

    private val viewModel: SearchViewModel by viewModels()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        val searchBar = view.findViewById<EditText>(R.id.search_bar)

        // Observe the search results
        lifecycleScope.launchWhenStarted {
            viewModel.searchResults.collect { results ->
                // Update the UI with the results
                updateRecyclerView(results)
            }
        }

        // Handle text input with debounce
        searchBar.addTextChangedListener { text ->
            viewModel.onSearchQueryChanged(text.toString())
        }
    }

    private fun updateRecyclerView(results: List<String>) {
        // Update RecyclerView or UI with search results
        // Adapter setup for displaying the search results
    }
}

In this code:

  1. ViewModel: We use MutableStateFlow to capture the search query input. The debounce(500) ensures that the flow will only emit after 500 milliseconds of no new emissions (i.e., no new characters typed).
  2. Fetching Results: Once the debounce period ends, we use flatMapLatest to fetch the search results from a repository (simulated with a delay).
  3. UI: The Fragment observes the search results and updates the UI with the results from the flow.

Why Use debounce in Android?

  1. Improve Performance: Preventing multiple API calls or data processing tasks that may arise from rapid user input (e.g., search queries, button clicks).
  2. Reduce Redundant Work: If the user changes input quickly, the app will only respond to the final input after the debounce period, reducing unnecessary operations.
  3. Smooth User Experience: It helps create a smoother user experience by avoiding overloading the system with requests or operations on every keystroke or event.

Conclusion

The debounce operator in Kotlin Flow is a powerful tool for managing rapid user input, events, or data emissions in Android development. Introducing a delay between events ensures that your app only responds to the final event after a specified idle period, reducing redundant operations and improving performance.


Bonus Tip: You can also combine debounce with other flow operators, such as distinctUntilChanged, retry, or combine, to further enhance its functionality and effectively handle more complex use cases.


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 improve my posts! ðŸ‘‡. Happy coding! ðŸ’»

Jetpack Compose Memory Issues: Causes, Impact, and Best Solutions

Memory performance is crucial to Android app development, especially when using Jetpack Compose. Inefficient memory management can lead to high memory usage, performance bottlenecks, and even app crashes due to OutOfMemoryError. In this article, we’ll explore the key moments when memory performance issues occur in Jetpack Compose, their root causes, and practical solutions to optimize memory usage.


When Do Memory Performance Issues Occur?

1. Unnecessary Recompositions

Jetpack Compose follows a declarative UI paradigm, where the UI updates when the state changes. However, inefficient recompositions can increase memory usage.

  • Occurs When:

    • misusing mutable states.
    • Not specifying keys in lists.
    • Using remember and rememberSaveable improperly.
  • Example of Bad Practice:

    @Composable
    fun Counter() {
        var count by remember { mutableStateOf(0) }
        Text(text = "Count: $count")
        Button(onClick = { count++ }) {
            Text("Increase")
        }
    }
    

    Here, every button click triggers a recomposition of the entire function.

  • Solution: Use remember Correctly

    @Composable
    fun Counter() {
        var count by remember { mutableStateOf(0) }
        Column {
            Text(text = "Count: $count")
            Button(onClick = { count++ }) {
                Text("Increase")
            }
        }
    }
    

    Now, only Text inside the Column is recommended when the count changes.


2. Large Image and Resource Loading

Mishandling images in Jetpack Compose can lead to excessive memory consumption.

  • Occurs When:

    • Loading high-resolution images without downscaling.
    • Keeping unnecessary image references in memory.
  • Example of Inefficient Image Handling:

    Image(
        painter = painterResource(id = R.drawable.large_image),
        contentDescription = "Large Image",
        modifier = Modifier.fillMaxSize()
    )
    
  • Solution: Use coil for Efficient Image Loading

    AsyncImage(
        model = ImageRequest.Builder(LocalContext.current)
            .data("https://example.com/large_image.jpg")
            .memoryCacheKey("large_image")
            .crossfade(true)
            .build(),
        contentDescription = "Large Image",
        modifier = Modifier.fillMaxSize()
    )
    

    Why? Coil automatically caches and optimizes image loading, reducing memory footprint.


3. Holding References to Large Objects

If an object is stored persistently in memory without proper cleanup, it can lead to memory leaks.

  • Occurs When:

    • Using remember without DisposableEffect or LaunchedEffect.
    • Keeping references to Activity or Context in composables.
  • Example of Memory Leak:

    val context = LocalContext.current
    val activity = context as Activity // Leaking the activity reference
    
  • Solution: Use Weak References

    @Composable
    fun SafeContextUsage() {
        val context = LocalContext.current.applicationContext // Avoid holding activity reference
    }
    

4. Misusing Coroutines in Jetpack Compose

Misusing coroutines can cause unnecessary memory consumption.

  • Occurs When:

    • Launching long-running coroutines in recomposing composables.
    • Forgetting to cancel coroutines.
  • Bad Practice (Coroutine Leak):

    @Composable
    fun FetchData() {
        val scope = CoroutineScope(Dispatchers.IO)
        scope.launch {
            // API call
        }
    }
    

    Here, a new coroutine scope is created every time the function recomposes.

  • Solution: Use LaunchedEffect

    @Composable
    fun FetchData() {
        LaunchedEffect(Unit) {
            // API call runs only once
        }
    }
    

    This ensures the coroutine starts only once per composition.


5. Using Large Lists Without Optimization

Rendering large lists without optimizations can cause high memory usage and laggy performance.

  • Occurs When:

    • Not using LazyColumn or LazyRow.
    • Keeping a large dataset in memory.
  • Bad Practice (Non-Optimized List):

    Column {
        items.forEach { item ->
            Text(text = item.name)
        }
    }
    

    This loads all items at once, increasing memory usage.

  • Solution: Use LazyColumn with Keys

    LazyColumn {
        items(items, key = { it.id }) { item ->
            Text(text = item.name)
        }
    }
    

    Why? LazyColumn only renders visible items, reducing memory usage.


Summary

Memory performance in Jetpack Compose can be impacted by improper state management, excessive recompositions, large object references, inefficient coroutine usage, and unoptimized lists. You can ensure a smooth and memory-efficient Android app by following best practices like using remember correctly, optimizing image loading, avoiding memory leaks, managing coroutines properly, and leveraging LazyColumn.

By proactively handling these issues, your app will perform better and offer a seamless user experience with optimal resource utilization.


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 so that I can improve my posts! 👇. Happy coding! 💻

Bit Manipulation - Finding the missing number in a sequence in Kotlin


Problem Statement:

You are given an array containing n distinct numbers from 0 to n. Exactly one number in this range is missing from the array. You must find this missing number using bit manipulation techniques.

Example:

Input: [3, 0, 1]
Output: 2

Input: [9,6,4,2,3,5,7,0,1]
Output: 8

Explanation (using XOR):

A very efficient way to solve this using bit manipulation is to leverage XOR (^), which has these properties:

  • a ^ a = 0 (XOR of a number with itself is zero)
  • a ^ 0 = a (XOR of a number with zero is itself)
  • XOR is commutative and associative

Therefore, if we XOR all the indices and all the numbers, every number present will cancel out, leaving the missing number.


Implementation in Kotlin:

fun missingNumber(nums: IntArray): Int {
    var xor = nums.size // start with n, since array is from 0 to n
    for (i in nums.indices) {
        xor = xor xor i xor nums[i]
    }
    return xor
}

fun main() {
    println(missingNumber(intArrayOf(3, 0, 1))) // Output: 2
    println(missingNumber(intArrayOf(9,6,4,2,3,5,7,0,1))) // Output: 8
    println(missingNumber(intArrayOf(0,1))) // Output: 2
}

Complexity:

  • Time Complexity: O(n) (Iterates through the array once)
  • Space Complexity: O(1) (No extra space used)


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 improve my posts! ðŸ‘‡ðŸš€. Happy coding! ðŸ’»✨

Difference Between observeAsState and collectAsState in Android Kotlin

Jetpack Compose, Google's modern UI toolkit for Android, simplifies state management by leveraging declarative programming. When dealing with state changes in Compose, developers often encounter two commonly used functions: observeAsState() and collectAsState(). Understanding their differences is crucial to building efficient and reactive UI components.

In this article, we will explore these functions, their use cases, and a practical example demonstrating their behavior. We will also discuss which one is better suited for different scenarios in an Android app.

What is observeAsState()?

observeAsState() is used to observe LiveData inside a composable function. It converts a LiveData object into a Compose State<T>, making integrating LiveData-based state management into a Compose UI easier.

Syntax:

@Composable
fun MyScreen(viewModel: MyViewModel) {
    val uiState by viewModel.uiState.observeAsState()
    
    Text(text = uiState ?: "Loading...")
}

When to Use?

  • When your ViewModel exposes a LiveData object.
  • If your app follows the traditional MVVM architecture with LiveData.
  • When you need automatic lifecycle awareness without additional coroutine handling.

What is collectAsState()?

collectAsState() is used to collect Flow inside a composable function and represent it as State<T>. Since Flow is more modern and supports reactive stream processing, it is a preferred choice for state management.

Syntax:

@Composable
fun MyScreen(viewModel: MyViewModel) {
    val uiState by viewModel.uiStateFlow.collectAsState()
    
    Text(text = uiState)
}

When to Use?

  • When your ViewModel exposes a Flow instead of LiveData.
  • If you prefer a modern, coroutine-based approach for state management.
  • When you need fine-grained control over data streams, such as handling backpressure or retry mechanisms.

Practical Example: Comparing observeAsState() and collectAsState()

Let’s compare these functions with a simple ViewModel that exposes both LiveData and Flow:

class MyViewModel : ViewModel() {
    private val _uiStateLiveData = MutableLiveData("Hello from LiveData")
    val uiStateLiveData: LiveData<String> = _uiStateLiveData

    private val _uiStateFlow = MutableStateFlow("Hello from Flow")
    val uiStateFlow: StateFlow<String> = _uiStateFlow
}

Composable Function Using observeAsState()

@Composable
fun LiveDataExample(viewModel: MyViewModel) {
    val uiState by viewModel.uiStateLiveData.observeAsState()
    
    Text(text = uiState ?: "Loading...")
}

Composable Function Using collectAsState()

@Composable
fun FlowExample(viewModel: MyViewModel) {
    val uiState by viewModel.uiStateFlow.collectAsState()
    
    Text(text = uiState)
}

Key Differences

Feature observeAsState() collectAsState()
Backed by LiveData Flow
Threading Runs on the Main thread Requires CoroutineContext
Lifecycle-aware Yes Yes
Performance Slightly less efficient More efficient for reactivity
Best for Legacy MVVM with LiveData Modern apps with Kotlin Flow

Which One is Better for Your App?

It depends on your app’s architecture and use case:

  • If your app is already using LiveData extensively, stick with observeAsState() to maintain consistency.
  • If your app is using Kotlin Flow, prefer collectAsState() since it is more performant and offers better stream handling capabilities.
  • For new projects, consider using Flow and collectAsState() as it aligns better with modern Android development best practices.

Summary

Both observeAsState() and collectAsState() serve similar purposes—updating the UI reactively in Jetpack Compose. However, observeAsState() is best for legacy projects that use LiveData, while collectAsState() is ideal for modern, coroutine-based architectures. By choosing the right approach, you can ensure a smooth and efficient Compose-based UI experience.

Would you like to explore deeper performance benchmarks or specific edge cases? Let me know in the comments!

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 improve my posts! ðŸ‘‡ðŸš€. Happy coding! ðŸ’»✨


Count Occurrences in List item and String in KOTLIN

 



Here is some way to find out how to count list item from the list, only first char in list and many more;

1. Count first character with start particular char on list 

fun countOccurrencesOnlyFirstCharUsingCount() {
val list = listOf("one", "two", "three", "four", "five", "six",
"seven", "eight", "nine", "ten")
val char = 't'
val count = list.count { it.startsWith(char) }
println("$char => $count")
}

output:

/*
t => 3
*/

2. Count occurrence first all characters on list

fun countOccurrencesFirstCharUsingGroupingBy() {
val list = listOf("one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten")
val frequenciesByFirstChar = list.groupingBy { it.first() }.eachCount()
println(frequenciesByFirstChar) // {o=1, t=3, f=2, s=2, e=1, n=1}
}

output:

/*
{o=1, t=3, f=2, s=2, e=1, n=1}
*/


3. Count occurrence of word in list by using groupingBy and eachCount

fun countOccurrencesUsingGroupingBy() {
val list = listOf("usa", "china","japan","usa", "canada","usa","canada", "japan", "india","japan")
println(list.groupingBy { it }.eachCount())

}

output:

/*
{usa=3, china=1, japan=3, canada=2, india=1}
*/

4. Count occurrence of word in list by using  MutableMap and for loop

fun countOccurrencesUsingMutableMap() {
val list = listOf("usa", "china","japan","usa", "canada","usa","canada", "japan", "india","japan")
val countMap : MutableMap<String, Int> = HashMap()
for (item in list){
var count = countMap[item]
if (count == null) count = 0
countMap[item] = count + 1
}
println(countMap)
}

output:

/*
{usa=3, china=1, canada=2, japan=3, india=1}
*/

5. Count occurrence of word in list by using distinctCollections and, frequency

fun countOccurrencesUsingCollection() {
val list:List<String> = listOf("usa", "china","japan","usa", "canada","usa","canada", "japan", "india","japan")
for(i in list.distinct()){
println( "$i => ${Collections.frequency( list, i)}")
}
}

output:

/*
usa => 3
china => 1
japan => 3
canada => 2
india => 1
*/

6. Count occurrence of word in list without (expect) banned word or list   (.filterNotgroupingBy ,  eachCount)

with help fo HashSet.

fun countOccurrencesUsingGroupingByBannedItem() {
val list = listOf("usa", "china","japan","usa", "canada","usa","canada", "japan", "india","japan")

val banned = setOf("china","india")
var bannedSet = banned.toHashSet()

val wordCount = list.filterNot { it in bannedSet }.groupingBy { it } .eachCount()
println(wordCount)
}

output:

/*
{usa=3, japan=3, canada=2}
*/


HAPPY CODING :) 

FizzBuzz solutions in KOTLIN


 

The FizzBuzz problem is a classic test given in coding interviews. The task is simple: Print integers 1 to N, but print “Fizz” if an integer is divisible by 3, “Buzz” if an integer is divisible by 5, and “FizzBuzz” if an integer is divisible by both 3 and 5.

Doing a "3 and 5" test makes the code more readable -- with more duplication:

  if (theNumber is divisible by 3) and (theNumber is divisible by 5) then
	print "FizzBuzz"
  else if (theNumber is divisible by 3) then
	print "Fizz"
  else if (theNumber is divisible by 5) then
	print "Buzz"
  else /* theNumber is not divisible by 3 or 5 */
	print theNumber
  end if


Here is some way of doing fizzbuzz solutions

1. Print FizBuzz using when

//".......Using when......."
fun printFizzBuzzWhen() {
for (i in 1..30) {
val result: String =
when {
i % 15 == 0 -> "FizzBuzz"
i % 3 == 0 -> "Fizz"
i % 5 == 0 -> "Buzz"
else -> "$i"
}
print("$result ")
}
}

output:

1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz 16 17 Fizz 19 Buzz Fizz 22 23 Fizz Buzz 26 Fizz 28 29 FizzBuzz


2. Print on List using map

//".......Using map and print on list......."
fun printFizzBuzzMapOf() {
println((1..30)
.map {
i -> mapOf(0 to i,
i % 3 to "Fizz",
i % 5 to "Buzz",
i % 15 to "FizzBuzz")[0] })
}

output:

[1, 2, Fizz, 4, Buzz, Fizz, 7, 8, Fizz, Buzz, 11, Fizz, 13, 14, FizzBuzz, 16, 17, Fizz, 19, Buzz, Fizz, 22, 23, Fizz, Buzz, 26, Fizz, 28, 29, FizzBuzz]

3. Check number is FIZZ or BUZZ or FIZZBUZZ or not (return number)

/--------find fizz or buzz or fizzbuzz-----------------
fun findFizzBuzzWhen(num: Int): String {
return when {
(num % 3 == 0 && num % 5 == 0) -> "FizzBuzz"
(num % 3 == 0) -> "Fizz"
(num % 5 == 0) -> "Buzz"
else -> "$num"
}
}
println("${findFizzBuzzWhen(15)}");

output: FizzBuzz


Learn more about fizzbuzz sotluion: https://wiki.c2.com/?FizzBuzzTest