
Cat Quiz

Earths' for Everyone
Coral reefs, those bustling underwater metropolises of biodiversity, are in peril due to climate change. Rising ocean temperatures and increasing acidity levels threaten these delicate ecosystems. But there’s hope: enter coral restoration.
Local communities play a crucial role. Educating people about reef conservation and sustainable fishing practices ensures a holistic approach. Researchers collaborate with reef-dependent populations to secure a brighter future for coral reefs.
While the path is tough, these innovative coral restoration projects hold promise. As transplanted corals mature and spawn, they contribute to natural reef recovery, providing essential habitats for marine life. Let’s protect these underwater wonders for generations to come! 🌊🐠🌿
(1) Restoring Coral Reefs | NOAA Fisheries. https://www.fisheries.noaa.gov/national/habitat-conservation/restoring-coral-reefs.
(2) Research priorities to support coral reefs during rapid climate change …. https://journals.plos.org/climate/article?id=10.1371/journal.pclm.0000435.
(3) Restoring Coral Reefs | Ocean Today. https://oceantoday.noaa.gov/restoringcoralreefs/.
(4) Frontiers | Perspectives on the Use of Coral Reef Restoration as a …. https://www.frontiersin.org/journals/marine-science/articles/10.3389/fmars.2021.618303/full.
(5) Coral Reef Restoration: A guide to coral restoration method. https://www.unep.org/resources/report/coral-reef-restoration-guide-coral-restoration-method.
Thresholding is a technique that converts a grayscale or color image into a binary image, where each pixel is either black or white. Thresholding can be useful for image segmentation, edge detection, and other applications. In this blog post, I will show you how to perform different types of thresholding with OpenCV, a popular library for computer vision in Python.
Simple thresholding is the simplest way of thresholding, where we use a global value as a threshold. If the pixel value is below the threshold, it is set to black; otherwise, it is set to white. We can use the cv2.threshold
function to apply simple thresholding. The function takes four arguments: the source image, the threshold value, the maximum value, and the thresholding type. The function returns two values: the threshold value used and the thresholded image.
Here is an example of simple thresholding with OpenCV:import cv2 import numpy as np from matplotlib import pyplot as plt # Read the image and convert it to grayscale img = cv2.imread('ex2.jpg') img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # Apply simple thresholding with a threshold value of 127 and a maximum value of 255 ret, thresh = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY) # Show the original and thresholded images plt.subplot(121), plt.imshow(img, cmap='gray') plt.title('Original Image'), plt.xticks([]), plt.yticks([]) plt.subplot(122), plt.imshow(thresh, cmap='gray') plt.title('Thresholded Image'), plt.xticks([]), plt.yticks([]) plt.show()
The output of the code is:
We can see that the thresholded image has only two colors: black and white. The pixels that are below 127 are set to black, and the pixels that are above 127 are set to white.
Simple thresholding may not work well if the image has different lighting conditions in different regions. In that case, adaptive thresholding can help. Adaptive thresholding determines the threshold for each pixel based on a small region around it. This way, we can get different thresholds for different regions of the same image, which can improve the results for images with varying illumination.
To apply adaptive thresholding, we can use the cv2.adaptiveThreshold
function. The function takes six arguments: the source image, the maximum value, the adaptive method, the thresholding type, the block size, and a constant value. The function returns the thresholded image.
The adaptive method can be one of the following:
cv2.ADAPTIVE_THRESH_MEAN_C
: The threshold value is the mean of the neighborhood area minus the constant C.cv2.ADAPTIVE_THRESH_GAUSSIAN_C
: The threshold value is the weighted sum of the neighborhood area minus the constant C. The weights are a Gaussian window.The block size determines the size of the neighborhood area. It must be an odd number.
Here is an example of adaptive thresholding with OpenCV:import cv2 import numpy as np from matplotlib import pyplot as plt # Read the image and convert it to grayscale img = cv2.imread('ex2.jpg') img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # Apply simple thresholding with a threshold value of 127 and a maximum value of 255 ret, thresh1 = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY) # Apply adaptive thresholding with a block size of 11 and a constant of 2 # Use the mean method and the binary thresholding type thresh2 = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 11, 2) # Apply adaptive thresholding with a block size of 11 and a constant of 2 # Use the Gaussian method and the binary thresholding type thresh3 = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 2) # Show the original and thresholded images titles = ['Original Image', 'Simple Thresholding', 'Adaptive Mean Thresholding', 'Adaptive Gaussian Thresholding'] images = [img, thresh1, thresh2, thresh3] for i in range(4): plt.subplot(2, 2, i+1), plt.imshow(images[i], 'gray') plt.title(titles[i]), plt.xticks([]), plt.yticks([]) plt.show()
The output of the code is:
We can see that the adaptive thresholding images are better than the simple thresholding image, especially for the regions that have different lighting conditions. The adaptive mean thresholding image has some noise, while the adaptive Gaussian thresholding image is smoother.
Otsu’s thresholding is another way of thresholding, where we do not need to specify the threshold value manually. Instead, the algorithm finds the optimal threshold value that minimizes the within-class variance of the pixel values. Otsu’s thresholding can be useful for images that have a bimodal histogram, where the pixel values are clustered into two distinct groups.
To apply Otsu’s thresholding, we can use the same cv2.threshold
function as before, but with an extra flag: cv2.THRESH_OTSU
. The function will then ignore the threshold value argument and use Otsu’s algorithm to find the optimal threshold. The function will return the optimal threshold value and the thresholded image.
Here is an example of Otsu’s thresholding with OpenCV:import cv2 import numpy as np from matplotlib import pyplot as plt # Read the image and convert it to grayscale img = cv2.imread('ex2.jpg') img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # Apply simple thresholding with a threshold value of 127 and a maximum value of 255 ret1, thresh1 = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY) # Apply Otsu's thresholding with a maximum value of 255 # The threshold value will be determined by the algorithm ret2, thresh2 = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) # Show the original and thresholded images titles = ['Original Image', 'Simple Thresholding', 'Otsu\'s Thresholding'] images = [img, thresh1, thresh2] for i in range(3): plt.subplot(1, 3, i+1), plt.imshow(images[i], 'gray') plt.title(titles[i]), plt.xticks([]), plt.yticks([]) plt.show() # Print the threshold values print('Simple Thresholding:', ret1) print('Otsu\'s Thresholding:', ret2)
The output of the code is:
Simple Thresholding: 127.0
Otsu’s Thresholding: 121.0
We can see that the Otsu’s thresholding image is similar to the simple thresholding image, but with a slightly different threshold value. The Otsu’s algorithm found that 121 is the optimal threshold value for this image, which minimizes the within-class variance.
In this blog post, I have shown you how to perform different types of thresholding with OpenCV, such as simple thresholding, adaptive thresholding, and Otsu’s thresholding. Thresholding is a useful technique for image processing and computer vision, as it can help to segment, detect, and enhance images. I hope you have learned something new and useful from this post. If you have any questions or feedback, please feel free to leave a comment below. Thank you for reading!
Source: Conversation with Bing, 2/16/2024
(1) OpenCV: Image Thresholding. https://docs.opencv.org/3.4/d7/d4d/tutorial_py_thresholding.html.
(2) Image Thresholding in Python OpenCV – GeeksforGeeks. https://www.geeksforgeeks.org/image-thresholding-in-python-opencv/.
(3) OpenCV cv2.threshold() Function – Scaler Topics. https://www.scaler.com/topics/cv2-threshold/.
(4) Simple Thresholding with OpenCV and Python – Jeremy Morgan. https://www.jeremymorgan.com/tutorials/opencv/simple-thresholding/.
(5) github.com. https://github.com/alex-ta/ImageProcessing/tree/dfeb8b4d80bfc1cfda8ea0d39a38d090f7410d3b/test%20-%20Kopie.py.
(6) github.com. https://github.com/zkzk5214/Py_road/tree/a67edc1604d468a37ab967d35ab1dc1f7a4f7742/openCV%2F15_Threshold%2F15_2.py.
(7) github.com. https://github.com/samuelxu999/Research/tree/187965ff3c86a02c7f5cc853d4cdb77ec805a786/OpenCV%2Fpy_dev%2Fsrc%2Ftest_demo%2FImageProcessing%2FImageThresholding.py.
(8) github.com. https://github.com/jgj03/opencv_library_basics/tree/b10f5871c1d4c5169385275abab59712b9bca364/001_opencv_basics.py.
If you have a pet, you know how much they mean to you. They are part of your family and you want to keep them safe and healthy. But what if your pet gets injured or sick? Do you know what to do in an emergency?
Pet first aid is the immediate care you provide to your pet when they are hurt or ill until you can get them to a veterinarian. It can make a difference between life and death, recovery and disability, or comfort and pain for your pet.
In this blog post, we will cover some basic tips and skills for pet first aid that every pet owner should know.
It is a good idea to have a pet first aid kit at home and in your car, so you are prepared for any situation. You can buy a ready-made kit or make your own with some common items. Here are some things you should have in your pet first aid kitAd1:
CPR stands for cardiopulmonary resuscitation. It is a lifesaving technique that can help restore breathing and blood circulation in your pet if they stop breathing or their heart stops beating. CPR should only be performed if your pet is unconscious and has no pulse2.
To perform CPR on your pet, follow these steps2:
There are many situations where your pet may need first aid care. Some of them are:
The best way to keep your pet safe and healthy is to prevent accidents and emergencies from happening in the first place. Here are some tips to prevent common hazards for your pet4:
Pet first aid is an essential skill for every pet owner. It can help you save your pet’s life in an emergency or reduce their pain and suffering until you can get them to a veterinarian. By having a pet first aid kit, knowing how to perform CPR, treating common injuries and illnesses, and preventing accidents and emergencies, you can be prepared for any situation that may arise with your pet.
We hope this blog post has been helpful and informative for you. If you have any questions or comments, please feel free to leave them below. And remember, if your pet is in serious trouble, always call your veterinarian or an emergency clinic right away.
Thank you for reading and stay safe!
Auto Amazon Links: No products found.
Creational design patterns are a set of solutions to common software development problems that deal with how objects are being created. Using such patterns will ensure that your code is flexible and reusable, and that you avoid hard-coded dependencies and tight coupling between classes.
In this blog post, I will show you some of the most important and widely used creational design patterns in Kotlin Android. You will learn how to apply these patterns to your projects and how they can help you write better and more maintainable code. I will cover four creational design patterns:
The factory method pattern defines an interface for creating an object, but lets subclasses decide which class to instantiate. The abstract factory pattern provides an interface for creating families of related or dependent objects without specifying their concrete classes.
These patterns are useful when you want to decouple the creation of objects from their usage, or when you want to provide different implementations of the same interface depending on some conditions. For example, you can use these patterns to create different types of views or fragments based on the device configuration or user preferences.
In Kotlin, you can use the provider model to implement these patterns. The provider model is a way of creating objects using lambda expressions or function references that act as factories. For example, you can use a provider function to create different types of fragments based on a parameter:
// An interface for fragments that display some content interface ContentFragment { fun showContent(content: String) } // A concrete implementation of ContentFragment that shows content in a text view class TextFragment : Fragment(), ContentFragment { private lateinit var textView: TextView override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { return inflater.inflate(R.layout.text_fragment, container, false) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) textView = view.findViewById(R.id.text_view) } override fun showContent(content: String) { textView.text = content } } // Another concrete implementation of ContentFragment that shows content in a web view class WebFragment : Fragment(), ContentFragment { private lateinit var webView: WebView override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { return inflater.inflate(R.layout.web_fragment, container, false) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) webView = view.findViewById(R.id.web_view) } override fun showContent(content: String) { webView.loadUrl(content) } } // A provider function that returns a ContentFragment based on a parameter fun provideContentFragment(type: String): ContentFragment = when (type) { "text" -> TextFragment() "web" -> WebFragment() else -> throw IllegalArgumentException("Unknown type: $type") }
The singleton pattern ensures that a class has only one instance and provides a global point of access to it. This pattern is useful when you want to have a single source of truth for some data or functionality in your app. For example, you can use this pattern to create a repository that handles data access from different sources.
In Kotlin, you can use the object declaration to create a singleton class. The object declaration combines a class declaration and a single instance of that class into one expression. For example, you can use an object declaration to create a news repository that fetches data from a remote data source:
// A singleton class that acts as a repository for news data object NewsRepository { // A reference to the remote data source private val newsRemoteDataSource = // getDataSource() // A flow that emits the latest news from the remote data source val latestNews: Flow<List<ArticleHeadline>> = newsRemoteDataSource.latestNews // A function that returns the details of an article by its id suspend fun getArticleDetails(id: String): ArticleDetails { return newsRemoteDataSource.getArticleDetails(id) } }
The builder pattern separates the construction of a complex object from its representation so that the same construction process can create different representations. This pattern is useful when you want to create objects with many optional parameters or when you want to have more control over how the object is constructed. For example, you can use this pattern to create an alert dialog with various options.
In Kotlin, you can use named arguments and default values to implement this pattern. Named arguments allow you to specify the name of a parameter when calling a function, which makes the code more readable and avoids errors when there are many parameters. Default values allow you to omit some parameters when calling a function if they have a predefined value. For example, you can use named arguments and default values to create an alert dialog builder class:
// A class that represents an alert dialog with various options class AlertDialog( val title: String, val message: String, val positiveButton: String = "OK", val negativeButton: String? = null, val icon: Int? = null, val onPositiveClick: () -> Unit = {}, val onNegativeClick: () -> Unit = {} ) { // A function that shows the alert dialog on the screen fun show() { // Create and display an alert dialog using the Android SDK ... } } // A builder class that creates an AlertDialog instance using named arguments and default values class AlertDialogBuilder { // A function that returns an AlertDialog instance with the given parameters fun build( title: String, message: String, positiveButton: String = "OK", negativeButton: String? = null, icon: Int? = null, onPositiveClick: () -> Unit = {}, onNegativeClick: () -> Unit = {} ): AlertDialog { return AlertDialog( title = title, message = message, positiveButton = positiveButton, negativeButton = negativeButton, icon = icon, onPositiveClick = onPositiveClick, onNegativeClick = onNegativeClick ) } }
The dependency injection pattern is a technique whereby one object supplies the dependencies of another object. A dependency is an object that can be used (a service). An injection is the passing of a dependency to a dependent object (a client) that would use it.
This pattern is useful when you want to reduce coupling and increase testability between classes by delegating the responsibility of creating and providing dependencies to another object or framework. For example, you can use this pattern to inject dependencies into your activities or view models.
In Kotlin Android, you can use frameworks like Dagger or Koin to implement this pattern. These frameworks provide annotations or DSLs to define dependencies and inject them into your classes. For example, you can use Koin to inject dependencies into your view model:
// A class that represents a user profile view model with some dependencies class UserProfileViewModel( private val userRepository: UserRepository, private val analyticsService: AnalyticsService ) : ViewModel() { // Some view model logic using userRepository and analyticsService ... } // A module that defines dependencies using Koin DSL val appModule = module { // Define UserRepository as a singleton using factory function single<UserRepository> { UserRepositoryImpl(get()) } // Define AnalyticsService as a singleton using constructor injection single<AnalyticsService> { AnalyticsServiceImpl() } // Define UserProfileViewModel using constructor injection viewModel { UserProfileViewModel(get(), get()) } } // Start Koin with appModule in Application class class MyApp : Application() { override fun onCreate() { super.onCreate() startKoin { androidContext(this@MyApp) modules(appModule) } } } // Get UserProfileViewModel instance using Koin extension function in Activity class class UserProfileActivity : AppCompatActivity() { // Inject UserProfileViewModel private val viewModel by viewModel<UserProfileViewModel>() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_user_profile) // Use viewModel ... } }
In this blog post, you learned how to use creational design patterns in Kotlin Android. You learned how to apply these patterns to your projects and how they can help you write better and more maintainable code. You learned how to use factory and abstract factory (provider model) method, singleton, builder, and dependency injection patterns.
If you want to learn more about design patterns and other Kotlin features for Android development, check out these resources:
I hope you enjoyed this blog post and found it useful. Happy coding! 😊
Flow is a stream processing API in Kotlin developed by JetBrains1. It’s an implementation of the Reactive Stream specification, an initiative whose goal is to provide a standard for asynchronous stream processing1. Jetbrains built Kotlin Flow on top of Kotlin Coroutines, which means that you can use suspend functions to produce and consume values asynchronously2.
In this blog post, I will show you how to use Flow in your Android project to handle live data updates and endless streams of data. You will learn how to create flows, modify them, collect them, and use StateFlow and SharedFlow to share state and events across your app.
To create flows, use the flow builder APIs. The flow builder function creates a new flow where you can manually emit new values into the stream of data using the emit function2. For example, you can use a flow to receive live updates from a network API:
class NewsRemoteDataSource( private val newsApi: NewsApi, private val refreshIntervalMs: Long = 5000 ) { val latestNews: Flow<List<ArticleHeadline>> = flow { while(true) { val latestNews = newsApi.fetchLatestNews() emit(latestNews) // Emits the result of the request to the flow delay(refreshIntervalMs) // Suspends the coroutine for some time } } } // Interface that provides a way to make network requests with suspend functions interface NewsApi { suspend fun fetchLatestNews(): List<ArticleHeadline> }
The flow builder is executed within a coroutine. Thus, it benefits from the same asynchronous APIs, but some restrictions apply:
You can use various operators to transform or filter the values emitted by a flow. For example, you can use map to apply a function to each value, filter to remove unwanted values, or combine to merge two flows into one2. For example, you can use map to convert the list of article headlines into a list of article titles:
val latestNewsTitles: Flow<List<String>> = latestNews.map { headlines -> headlines.map { headline -> headline.title } }
You can also use operators that are specific to flows, such as debounce or distinctUntilChanged. These operators help you deal with flows that emit values too frequently or unnecessarily2. For example, you can use debounce to ignore values that are emitted in quick succession:
val debouncedNewsTitles: Flow<List<String>> = latestNewsTitles.debounce(1000) // Ignores values that are emitted less than 1000 ms apart
To start receiving values from a flow, you need to collect it. Collecting is a terminal operation that triggers the execution of the flow and invokes a given action for every value emitted by the flow2. You need to collect flows from a coroutine or a suspend function. For example, you can collect the debounced news titles from an activity:
class LatestNewsActivity : AppCompatActivity() { private val newsRemoteDataSource = // getDataSource() override fun onCreate(savedInstanceState: Bundle?) { ... // Start a coroutine in the lifecycle scope lifecycleScope.launch { // repeatOnLifecycle launches the block in a new coroutine every time the // lifecycle is in the STARTED state (or above) and cancels it when it's STOPPED. repeatOnLifecycle(Lifecycle.State.STARTED) { // Trigger the flow and start listening for values. newsRemoteDataSource.debouncedNewsTitles.collect { titles -> // Update UI with new titles } } } } }
Note that collecting from a flow can be a suspending operation if the flow is infinite or slow. This means that you should not collect from multiple flows sequentially in the same coroutine, as this will block the execution of the next collect until the previous one finishes. Instead, you should launch multiple coroutines or use other operators like zip or flatMapMerge to collect from multiple flows concurrently2.
StateFlow and SharedFlow are Flow APIs that enable flows to optimally emit state updates and emit values to multiple consumers3.
StateFlow is a state-holder observable flow that emits the current and new state updates to its collectors. The current state value can also be read through its value property. To update state and send it to the flow, assign a new value to the value property of the MutableStateFlow class3.
In Android, StateFlow is a great fit for classes that need to maintain an observable mutable state. For example, you can use StateFlow to expose UI state from a ViewModel:
class LatestNewsViewModel( private val newsRepository: NewsRepository ) : ViewModel() { // Backing property to avoid state updates from other classes private val _uiState = MutableStateFlow(LatestNewsUiState.Success(emptyList())) // The UI collects from this StateFlow to get its state updates val uiState: StateFlow<LatestNewsUiState> = _uiState init { viewModelScope.launch { newsRepository.favoriteLatestNews // Update View with the latest favorite news // Writes to the value property of MutableStateFlow, // adding a new element to the flow and updating all // of its collectors .collect { favoriteNews -> _uiState.value = LatestNewsUiState.Success(favoriteNews) } } } } // Represents different states for the LatestNews screen sealed class LatestNewsUiState { data class Success(val news: List<ArticleHeadline>): LatestNewsUiState() data class Error(val exception: Throwable): LatestNewsUiState() }
Unlike a cold flow built using the flow builder, a StateFlow is hot: collecting from the flow doesn’t trigger any producer code. A StateFlow is always active and in memory, and it becomes eligible for garbage collection only when there are no other references to it from a garbage collection root. When a new consumer starts collecting from the flow, it receives the last state in the stream and any subsequent states. You can find this behavior in other observable classes like LiveData3.
SharedFlow is an observable hot flow that emits values only when active collectors are present. Unlike StateFlow, SharedFlow does not have any initial value nor does it store any value at all. To emit values into SharedFlow use its emit function3.
In Android, SharedFlow is useful for sharing events among multiple consumers without having any initial value or state associated with them. For example, you can use SharedFlow to broadcast user input events across your app:
class UserInputManager { // Creates an instance of MutableSharedFlow with zero replay buffer size, // meaning that only new events will be emitted by this SharedFlow. private val _userInputEvents = MutableSharedFlow<UserInputEvent>() // Exposes only SharedFlow interface so other classes cannot modify it. val userInputEvents: SharedFlow<UserInputEvent> = _userInputEvents fun onUserInput(event: UserInputEvent) { viewModelScope.launch { _userInputEvents.emit(event) // Emits event into SharedFlow } } } // Represents different types of user input events sealed class UserInputEvent { data class Tap(val x: Float, val y: Float): UserInputEvent() data class Swipe(val direction: Direction): UserInputEvent() }
To collect from StateFlow or SharedFlow, you can use any terminal operator like collect or first as with any other flow.
In this blog post, you learned how to use Flow in your Android project to handle live data updates and endless streams of data. You learned how to create flows, modify them, collect them, and use StateFlow and SharedFlow to share state and events across your app.
If you want to learn more about Flow and other Kotlin features for Android development, check out these resources:
I hope you enjoyed this blog post and found it useful. Happy coding! 😊
Nullability and Common Extension Functions in Kotlin Explained with Code Examples
Kotlin is a statically typed programming language that was designed to be a safer alternative to Java. One of the ways it achieves this safety is through nullability, which allows developers to prevent null pointer exceptions at runtime by making them compile-time errors. In this blog post, we will explore nullability in Kotlin and common extension functions that can help you work with null values more efficiently.
Nullability in Kotlin
Nullability is a concept in programming that refers to the ability of a variable or object to hold a null value. In Kotlin, nullability is controlled with two types: nullable and non-nullable. A nullable type can hold a null value, while a non-nullable type cannot.
To make a variable or object nullable in Kotlin, you simply add a question mark after its type. For example, the following code creates a nullable String:
var nullableString: String? = null
To make a variable or object non-nullable in Kotlin, you do not add a question mark after its type. For example, the following code creates a non-nullable String:
val nonNullableString: String = “Hello, World!”
If you try to assign a null value to a non-nullable type, the compiler will give you an error:
val nonNullableString: String = null // Error: Null can not be value of a non-null type String
Common Extension Functions for Nullability
There are several extension functions in Kotlin that can help you work with null values more efficiently. Let’s look at some of the most common ones:
1. safeCall
The safeCall function allows you to execute a method or property on a nullable object without the risk of a null pointer exception. If the object is null, the function returns null.
For example, the following code uses the safeCall function to print the length of a nullable String:
val nullableString: String? = null
println(nullableString?.length) // Prints null
2. Elvis Operator
The Elvis operator allows you to assign a default value to a nullable variable or object. If the value is null, the operator returns the default value instead.
For example, the following code uses the Elvis operator to assign a default value to a nullable String:
val nullableString: String? = null
val length = nullableString?.length ?: -1
println(length) // Prints -1
3. Let Function
The let function allows you to execute a block of code if a nullable variable or object is not null. Inside the block, the variable or object is called with the it keyword.
For example, the following code uses the let function to print the length of a nullable String if it is not null:
val nullableString: String? = “Hello, World!”
nullableString?.let { println(it.length) } // Prints 13
4. Nullable Type Conversion
The toIntOrNull and toDoubleOrNull functions allow you to convert a nullable String to an Int or Double, respectively. If the String is null or cannot be parsed, the functions return null.
For example, the following code converts a nullable String to an Int using the toIntOrNull function:
val nullableString: String? = “123”
val nullableInt: Int? = nullableString?.toIntOrNull()
println(nullableInt) // Prints 123
In conclusion, nullability is an essential concept in Kotlin that helps prevent null pointer exceptions. By using common extension functions like safeCall, Elvis operator, Let function, and nullable type conversion, you can work with null values more efficiently and safely. We hope this blog post has been helpful in understanding nullability in Kotlin and its common extension functions.
Test Driven Development or TDD can be a useful deterrent for bugs in huge codebases. Writing tests can get complex when trying to introduce them into an existing codebase, especially if there is tightly coupled classes. One way to start doing TDD in an existing codebase is to start introducing unit tests for each bug that the engineer is working on. Ideally every time an engineer is fixing a bug, they would make the unit test that would verify the expected behavior, then once they have the unit test failing, they would actually fix the bug. If engineers took this approach to start doing TDD in an existing codebase, eventually code coverage will grow as the bugs are resolved one by one.
One problem with introducing TDD into an existing project is the testability of the code. Depending on the quality of the architecture and design patterns used in the codebase and amount of coupling, the engineer might have to do substantial refactoring to make the code testable. Either way refactoring code to be testable is actually a major improvement which will facilitate more ease of development. Other engineers will be more confident that they will not break other parts of code once tests are introduced from other TDD tasks and code coverage climbs.
TDD can be a very powerful way to ensure quality of production code and engineers will have to deal with a great deal less of bugs in the long run. At first doing TDD in an existing codebase will take longer than if it was done since the beginning of writing the application. Once the coverage gets to a certain point in an application, the code should be refactored to be testable enough that engineers will take less time to introduce new tests and code.