Amol Pawar

TOML Numbers

Master TOML Numbers: Ultimate Guide to Integers & Floats

TOML (Tom’s Obvious, Minimal Language) is a popular configuration file format known for its simplicity and readability. It is widely used in various applications for defining configurations in a structured manner. One of the fundamental data types in TOML is numbers, which can be categorized into integers and floats. In this blog, we will explore TOML numbers in depth with clear explanations and practical examples.

Understanding TOML Numbers

TOML supports two primary numeric types:

  • Integers: Whole numbers without a fractional or decimal part.
  • Floats: Numbers that include decimal points or are represented in scientific notation.

These numbers are used in configuration files for various applications, such as specifying limits, settings, or numeric identifiers.

TOML Integers

Integers in TOML are whole numbers. They can be:

  • Positive (e.g., +99, 42)
  • Negative (e.g., -17)
  • Zero (e.g., 0, +0, -0 → all are the same)
TOML
int1 = +99
int2 = 42
int3 = 0
int4 = -17

Note – Positive integers can be prefixed with a plus sign, while negative integers are always prefixed with a minus sign. Additionally, the integer values -0 and +0 are valid and identical to an unprefixed zero.

Large Numbers with Underscores

For large numbers, underscores may be used between digits to enhance readability. Each underscore must be surrounded by at least one digit on both sides. That means you can use underscores (_) between digits to improve readability, but they must not appear at the start or end of the number (e.g., 1_000_000 is valid, but _1000 is not).

TOML
int1 = 1_000       # Same as 1000
int2 = 5_349_221   # More readable
int3 = 53_49_221   # Indian-style grouping

int4 = 1_2_3_4_5   # Allowed, but not recommended

We’ll look at a few examples of valid and invalid numbers in just a moment. But let’s first see, what exactly is Indian-style grouping? As an Indian, I’m pretty familiar with it, but I realize that not everyone might be, so let me break it down for them. By the way, I’m really proud of my heritage, and I always enjoy sharing a bit of our culture, ethics, and values with the world whenever I can. I think we all feel that way, right..?

So, what is Indian number system grouping?

The Indian number system is a way of grouping numbers that is different from the international system used in many other countries. The key difference lies in how large numbers are grouped and named.

Grouping:

  • First three digits: The first three digits from the right are grouped together (ones, tens, hundreds).
  • Subsequent groups: After the first three digits, the remaining digits are grouped in sets of two.

For example:

  • 1,00,000 (one lakh) instead of 100,000
  • 10,00,000 (ten lakh) instead of 1,000,000
  • 1,00,00,000 (one crore) instead of 10,000,000

Commas are used to separate the groups of digits. The first comma is placed after the first three digits from the right, and subsequent commas are placed after every two digits.

The number 123,456,789 would be written as 12,34,56,789 in the Indian number system.

In short, the Indian number system has a unique way of grouping digits and uses specific place values to represent large numbers. It is typically used in neighboring countries like Nepal, Bangladesh, Pakistan, and other South Asian countries.

Valid Integers

TOML
positive_int = 42
negative_int = -100
large_number = 1_000_000  # Readable format

Invalid Integers

TOML
invalid_int = 1.0  # Not an integer (it's a float)
leading_zero = 007  # Leading zeros are NOT allowed

Leading Zeros: TOML does not allow integers to start with 0 unless the value is 0 itself.

Other Number Formats

TOML supports hexadecimal, octal, and binary for non-negative integers.

  • Hexadecimal (base 16) → 0x prefix
  • Octal (base 8) → 0o prefix
  • Binary (base 2) → 0b prefix

Key Rules:

  • No + sign is allowed at the beginning.
  • Leading zeros are allowed (after the prefix).
  • Hexadecimal values are not case-sensitive (uppercase and lowercase letters work the same).
  • Underscores (_) can be used to improve readability, but not between the prefix and the number.
TOML
# Hexadecimal (Base 16) – Prefix: 0x

hex1 = 0xDEADBEEF  # Same as 3735928559 in decimal
hex2 = 0xdeadbeef  # Same as above, case-insensitive
hex3 = 0xdead_beef # Same as above, underscore for readability



# Octal (Base 8) – Prefix: 0o

oct1 = 0o01234567  # Same as 342391 in decimal
oct2 = 0o755       # Common in Unix file permissions



# Binary (Base 2) – Prefix: 0b

bin1 = 0b11010110  # Same as 214 in decimal

Integer Limits

  • TOML supports 64-bit signed integers (from -2^63 to 2^63 - 1), meaning numbers can range from −9,223,372,036,854,775,808 to 9,223,372,036,854,775,807.
  • If a number goes beyond this range, it cannot be stored correctly (some digits would be lost).
  • In such cases, TOML must show an error instead of trying to adjust or round the number.

Valid 64-bit signed integers

TOML
small_number = -9223372036854775808  # Minimum value
large_number = 9223372036854775807   # Maximum value

Invalid (too large or too small)

TOML
too_large = 9223372036854775808  # Error! Exceeds the maximum limit
too_small = -9223372036854775809 # Error! Below the minimum limit

If you try to use a number outside this range, TOML must stop and show an error instead of storing an incorrect value.

Please note that some TOML parsers or validators might accept values beyond this limit. However, those that strictly follow the specification will show errors if the limit is exceeded.

TOML Floats

TOML floats follow the IEEE 754 binary64 format (same as double-precision floating-point numbers in many programming languages).

How Floats Are Written in Toml:

A float must have an integer part, followed by:

  1. A fractional part (a decimal point + digits) OR
  2. An exponent part (E or e + integer) OR
  3. Both (fractional part first, then exponent).

Valid Floats

TOML
# Floats with a Fractional Part

flt1 = +1.0      # Positive float
flt2 = 3.1415    # Pi approximation
flt3 = -0.01     # Negative float



# Floats with an Exponent Part (scientific notation)

flt4 = 5e+22  # 5 × 10^22
flt5 = 1e06   # 1 × 10^6 (same as 1,000,000)
flt6 = -2E-2  # -2 × 10^(-2) (same as -0.02)



# Floats with Both Fractional and Exponent Parts

flt7 = 6.626e-34  # 6.626 × 10^-34 (Planck's constant)

Invalid floats

TOML
# INVALID examples

invalid_float_1 = .7     # Missing whole number (No digit before the decimal)
invalid_float_2 = 7.     # Missing fractional part (No digit after the decimal)
invalid_float_3 = 3.e+20 # Missing fractional part before 'e' (No digit after the decimal)

Rule: A decimal point must have digits on both sides. This means a float must have digits on both sides of the decimal point.

Readability with Underscores

Similar to integers, underscores may be used to enhance readability. Each underscore must be surrounded by at least one digit.

TOML
# Readable Floats Using Underscores

flt8 = 224_617.445_991_228 # Underscores improve readability

Rule: Underscores must be between numbers, not at the start or end.

Zero in Toml Float

In TOML, the values -0.0 and +0.0 are valid and represent zero, but with a sign.

  • -0.0: Negative zero
  • +0.0: Positive zero
  • 0.0: Zero without any sign

According to the IEEE 754 standard for floating-point numbers:

  • -0.0 and +0.0 are treated as the same value (0.0), but they have different internal representations, which can be important in certain calculations.
  • The sign of zero may affect certain edge cases in mathematical operations, but for most practical uses, they are treated the same as 0.0.

This is mostly used in scientific computing or cases where the sign of zero can have meaning.

Special Float Values

TOML also supports special float values, which are always written in lowercase.

Infinity (inf)

  • inf: Represents positive infinity.
  • +inf: Same as inf, positive infinity (optional + sign).
  • -inf: Represents negative infinity.
TOML
# infinity

sf1 = inf  # positive infinity
sf2 = +inf # positive infinity (same as inf)
sf3 = -inf # negative infinity

Infinity is often used in mathematical operations to represent values that exceed any finite number.

Not-a-Number (nan)

  • nan: Represents “Not a Number,” which is used to signal invalid or undefined numbers (like dividing zero by zero).
  • +nan: Same as nan, but with an optional plus sign.
  • -nan: Same as nan, but with a minus sign.
TOML
# not a number, 0 / 0 results in nan because there's no defined answer to that division.

sf4 = nan  # actual sNaN/qNaN encoding is implementation-specific
sf5 = +nan # same as `nan`
sf6 = -nan # valid, actual encoding is implementation-specific

NaN is used when a result doesn’t make sense (like dividing zero by zero). The way NaN (Not-a-Number) is encoded depends on the implementation, so how it’s stored can vary. This means how NaN is stored in memory (internally) depends on the system or language you’re using.
There are two common types of NaN:

  • sNaN (Signaling NaN): Used to signal an error in a computation (usually a floating-point exception or to trigger a trap, depending on the system).
  • qNaN (Quiet NaN): Used to propagate through calculations without signaling an error (i.e., it propagates “quietly” through calculations without raising exceptions).

However, for TOML, it doesn’t matter whether it’s sNaN or qNaN; it simply stores it as nan, and how it’s handled is up to the implementation (such as in programming languages or hardware).

Practical Example: Using Numbers in a TOML Config File

Here’s a simple example of a TOML configuration file (for demonstration purposes only) that uses both integers and floats:

TOML
# Database configuration
db_max_connections = 100
query_timeout = 30.5  # Float value in seconds

# Application settings
retry_attempts = 5
cache_expiry = 60.0  # Float value, ensures decimal representation

# Science-related values
pi_value = 3.14159
speed_of_light = 2.99792458e8  # Scientific notation

Here,

  • db_max_connections is an integer representing the maximum number of connections.
  • query_timeout is a float because time values often require more precision.
  • retry_attempts is an integer because you can’t retry a fractional number of times.
  • cache_expiry is written as 60.0 to explicitly indicate it’s a float, as it represents time, and it’s good practice to allow for fractional values in time settings. This way, you can be more precise with your configuration.
  • pi_value and speed_of_light demonstrate precision and scientific notation.

Best Practices for Using Numbers in TOML

  1. Use underscores (_) for readability in large numbers.
  2. Use floats where precision matters, such as timeouts or scientific values.
  3. Stick to integer values when working with counts, IDs, or whole numbers.
  4. Avoid leading zeros in integers.
  5. Use scientific notation (e) only when necessary, as it may not always be clear.

Conclusion

TOML provides a simple yet powerful way to define numerical values. By understanding integers and floats, you can create clear, well-structured TOML configuration files that are both human-readable and machine-friendly. Whether you’re configuring application settings, scientific data, or system parameters, following best practices will help maintain accuracy and readability.

By keeping these principles in mind, you’ll be able to work with TOML numbers effectively and avoid common pitfalls.

TOML Floats

Master TOML Floats: Ultimate Guide to IEEE 754 Representation

TOML (Tom’s Obvious, Minimal Language) is a configuration language that’s designed to be simple and easy to understand, with a focus on usability. One of the primary data types in TOML is float, which is used to represent decimal numbers with a fractional part. These float values in TOML are implemented as IEEE 754 binary64 values. In this blog, we’ll dive into how Toml floats work in TOML, with particular emphasis on their syntax, representation, and the rules that define valid and invalid float values.

TOML Floats

In TOML, a float is a numerical value that can represent real numbers, including fractional values and those expressed in scientific notation (with exponents).

A float consists of three parts:

  1. Integer Part: The whole number part of the float.
  2. Fractional Part: The part after the decimal point.
  3. Exponent Part: The part that shows how the number should be scaled, based on the exponent.

The IEEE 754 binary64 standard (commonly known as double-precision floating point) is used for encoding these values. This standard defines how floating-point numbers are represented and how operations on them are performed.

Format of TOML Floats

A float in TOML follows a specific structure:

  1. Integer Part: The whole number before the decimal point.
  2. Fractional Part (optional): A decimal point followed by one or more digits.
  3. Exponent Part (optional): A scientific notation format represented by an “E” or “e” followed by a signed integer, indicating the power of 10 to which the number is raised.

Valid Floats

TOML
# Floats with a Fractional Part

flt1 = +1.0      # Positive float
flt2 = 3.1415    # Pi approximation
flt3 = -0.01     # Negative float



# Floats with an Exponent Part (scientific notation)

flt4 = 5e+22  # 5 × 10^22
flt5 = 1e06   # 1 × 10^6 (same as 1,000,000)
flt6 = -2E-2  # -2 × 10^(-2) (same as -0.02)



# Floats with Both Fractional and Exponent Parts

flt7 = 6.626e-34  # 6.626 × 10^-34 (Planck's constant)

Invalid floats

TOML
# INVALID examples

invalid_float_1 = .7     # Missing whole number (No digit before the decimal)
invalid_float_2 = 7.     # Missing fractional part (No digit after the decimal)
invalid_float_3 = 3.e+20 # Missing fractional part before 'e' (No digit after the decimal)

Rule: A decimal point must have digits on both sides. This means a float must have digits on both sides of the decimal point.

Readability with Underscores

Similar to integers, underscores may be used to enhance readability. Each underscore must be surrounded by at least one digit.

TOML
# Readable Floats Using Underscores

flt8 = 224_617.445_991_228 # Underscores improve readability

Rule: Underscores must be between numbers, not at the start or end.

Zero in Toml Float

In TOML, the values -0.0 and +0.0 are valid and represent zero, but with a sign.

  • -0.0: Negative zero
  • +0.0: Positive zero
  • 0.0: Zero without any sign

According to the IEEE 754 standard for floating-point numbers:

  • -0.0 and +0.0 are treated as the same value (0.0), but they have different internal representations, which can be important in certain calculations.
  • The sign of zero may affect certain edge cases in mathematical operations, but for most practical uses, they are treated the same as 0.0.

This is mostly used in scientific computing or cases where the sign of zero can have meaning.

Special Float Values (IEEE 754)

TOML also supports special float values, which are always written in lowercase.

Infinity (inf)

  • inf: Represents positive infinity.
  • +inf: Same as inf, positive infinity (optional + sign).
  • -inf: Represents negative infinity.
TOML
# infinity

sf1 = inf  # positive infinity
sf2 = +inf # positive infinity (same as inf)
sf3 = -inf # negative infinity

Infinity is often used in mathematical operations to represent values that exceed any finite number.

Not-a-Number (nan)

  • nan: Represents “Not a Number,” which is used to signal invalid or undefined numbers (like dividing zero by zero).
  • +nan: Same as nan, but with an optional plus sign.
  • -nan: Same as nan, but with a minus sign.
TOML
# not a number, 0 / 0 results in nan because there's no defined answer to that division.

sf4 = nan  # actual sNaN/qNaN encoding is implementation-specific
sf5 = +nan # same as `nan`
sf6 = -nan # valid, actual encoding is implementation-specific

NaN is used when a result doesn’t make sense (like dividing zero by zero). The way NaN (Not-a-Number) is encoded depends on the implementation, so how it’s stored can vary. This means how NaN is stored in memory (internally) depends on the system or language you’re using.
There are two common types of NaN:

  • sNaN (Signaling NaN): Used to signal an error in a computation (usually a floating-point exception or to trigger a trap, depending on the system).
  • qNaN (Quiet NaN): Used to propagate through calculations without signaling an error (i.e., it propagates “quietly” through calculations without raising exceptions).

However, for TOML, it doesn’t matter whether it’s sNaN or qNaN; it simply stores it as nan, and how it’s handled is up to the implementation (such as in programming languages or hardware).

Conclusion

TOML provides a simple and intuitive way to represent float values, while still adhering to the powerful and widely adopted IEEE 754 binary64 standard. By understanding the basic rules of float formatting and the significance of fractional and exponent parts, you can effectively use and manage floating-point values in TOML configuration files. The ability to represent special values like infinity and NaN makes TOML even more flexible for a wide range of applications, particularly those that require precise and high-range numerical values.

Toml Integers

Understanding TOML Integers: A Comprehensive Guide

When it comes to configuration files, TOML (Tom’s Obvious, Minimal Language) is a popular choice because it’s simple and easy to use. It’s great for projects that need human-readable configs, and with support for various data types—including integers—it’s pretty versatile too.

TOML Integers

Integers in TOML are whole numbers. They can be:

  • Positive (e.g., +99, 42)
  • Negative (e.g., -17)
  • Zero (e.g., 0, +0, -0 → all are the same)
TOML
int1 = +99
int2 = 42
int3 = 0
int4 = -17

Note – Positive integers can be prefixed with a plus sign, while negative integers are always prefixed with a minus sign. Additionally, the integer values -0 and +0 are valid and identical to an unprefixed zero.

Large Numbers with Underscores

For large numbers, underscores may be used between digits to enhance readability. Each underscore must be surrounded by at least one digit on both sides. That means you can use underscores (_) between digits to improve readability, but they must not appear at the start or end of the number (e.g., 1_000_000 is valid, but _1000 is not).

TOML
int1 = 1_000       # Same as 1000
int2 = 5_349_221   # More readable
int3 = 53_49_221   # Indian-style grouping

int4 = 1_2_3_4_5   # Allowed, but not recommended

We’ll look at a few examples of valid and invalid numbers in just a moment. But let’s first see, what exactly is Indian-style grouping? As an Indian, I’m pretty familiar with it, but I realize that not everyone might be, so let me break it down for them. By the way, I’m really proud of my heritage, and I always enjoy sharing a bit of our culture, ethics, and values with the world whenever I can. I think we all feel that way, right..?

So, what is Indian number system grouping?

The Indian number system is a way of grouping numbers that is different from the international system used in many other countries. The key difference lies in how large numbers are grouped and named.

Grouping:

  • First three digits: The first three digits from the right are grouped together (ones, tens, hundreds).
  • Subsequent groups: After the first three digits, the remaining digits are grouped in sets of two.

For example:

  • 1,00,000 (one lakh) instead of 100,000
  • 10,00,000 (ten lakh) instead of 1,000,000
  • 1,00,00,000 (one crore) instead of 10,000,000

Commas are used to separate the groups of digits. The first comma is placed after the first three digits from the right, and subsequent commas are placed after every two digits.

The number 123,456,789 would be written as 12,34,56,789 in the Indian number system.

In short, the Indian number system has a unique way of grouping digits and uses specific place values to represent large numbers. It is typically used in neighboring countries like Nepal, Bangladesh, Pakistan, and other South Asian countries.

Valid Integers

TOML
positive_int = 42
negative_int = -100
large_number = 1_000_000  # Readable format

Invalid Integers

TOML
invalid_int = 1.0  # Not an integer (it's a float)
leading_zero = 007  # Leading zeros are NOT allowed

Leading Zeros: TOML does not allow integers to start with 0 unless the value is 0 itself.

Other Number Formats

TOML supports hexadecimal, octal, and binary for non-negative integers.

  • Hexadecimal (base 16) → 0x prefix
  • Octal (base 8) → 0o prefix
  • Binary (base 2) → 0b prefix

Key Rules:

  • No + sign is allowed at the beginning.
  • Leading zeros are allowed (after the prefix).
  • Hexadecimal values are not case-sensitive (uppercase and lowercase letters work the same).
  • Underscores (_) can be used to improve readability, but not between the prefix and the number.
TOML
# Hexadecimal (Base 16) – Prefix: 0x

hex1 = 0xDEADBEEF  # Same as 3735928559 in decimal
hex2 = 0xdeadbeef  # Same as above, case-insensitive
hex3 = 0xdead_beef # Same as above, underscore for readability



# Octal (Base 8) – Prefix: 0o

oct1 = 0o01234567  # Same as 342391 in decimal
oct2 = 0o755       # Common in Unix file permissions



# Binary (Base 2) – Prefix: 0b

bin1 = 0b11010110  # Same as 214 in decimal

Integer Limits

  • TOML supports 64-bit signed integers (from -2^63 to 2^63 - 1), meaning numbers can range from −9,223,372,036,854,775,808 to 9,223,372,036,854,775,807.
  • If a number goes beyond this range, it cannot be stored correctly (some digits would be lost).
  • In such cases, TOML must show an error instead of trying to adjust or round the number.

Valid 64-bit signed integers

TOML
small_number = -9223372036854775808  # Minimum value
large_number = 9223372036854775807   # Maximum value

Invalid (too large or too small)

TOML
too_large = 9223372036854775808  # Error! Exceeds the maximum limit
too_small = -9223372036854775809 # Error! Below the minimum limit

If you try to use a number outside this range, TOML must stop and show an error instead of storing an incorrect value.

Please note that some TOML parsers or validators might accept values beyond this limit. However, those that strictly follow the specification will show errors if the limit is exceeded.

Conclusion

TOML integers are simple, yet powerful. From basic whole numbers to more complex representations like hexadecimal, octal, and binary, TOML allows a variety of formats. It also ensures that large numbers are readable, either with underscores or through direct formatting, but it keeps the rules strict when it comes to leading zeros. By following these rules, you can write clear, precise configuration files that are easy to read and maintain.

Understanding the nuances of TOML’s integer syntax allows for cleaner and more readable configuration files, making the process of managing complex systems more efficient and error-free.

If you’re working with TOML and large datasets, remember the value of good readability practices. This small detail can greatly improve the developer experience and reduce mistakes in the long run.

Toml Strings

Understanding TOML Strings: A Comprehensive Guide

TOML (Tom’s Obvious, Minimal Language) is a configuration file format that is designed to be easy to read and write, with a focus on simplicity and clarity. It is widely used in applications where human-readable configurations are needed. In this blog, we will dive deep into TOML strings, their syntax, and how they can be used effectively.

TOML Strings

In TOML, strings are sequences of characters enclosed in quotes. They are used to represent textual data, such as names, URLs, file paths, or any other information that needs to be stored as plain text.

TOML supports the following types of strings:

  1. Basic Strings
  2. Multiline Basic Strings
  3. Literal Strings
  4. Multiline Literal Strings

Each of these string types serves a specific purpose and offers flexibility in handling text data. Let’s explore each type with examples and detailed explanations.

Basic Strings

Basic strings in TOML are enclosed in double quotes ("). These are ideal for simple textual data and support escape sequences.

Basic strings in TOML are enclosed in double quotes ("). These are ideal for simple textual data and support escape sequences for special characters, including:

Unicode characters: You can use almost any character from any language, including symbols and letters.

Escaped characters: Some characters have special meanings and cannot be used directly in a string. These include:

  • The quotation mark (")
  • The backslash (\)

Control characters: These are invisible characters such as newlines, tabs, and others in the range U+0000 to U+001F, U+007F (e.g., null, backspace).

If you need to include any of these characters in your string, you’ll need to escape them using a backslash (\). For example:

  • \" for a quotation mark.
  • \\ for a backslash.

Syntax

TOML
key = "This is a basic string."

Features

  • Escape sequences like \n, \t, and \" are supported.
  • Suitable for most common cases where special characters need to be encoded.
TOML
str = "I'm a string. \"You can quote me\". Name\tJos\u00E9\nLocation\tSF."

This given string would look like this when processed (it’s just for the sake of understanding).

Rust
I'm a string. "You can quote me". Name    José
Location    SF.

Here’s a simple explanation of the most popular escape sequences and how they work:

  1. \b – Backspace (U+0008)
    Removes the previous character, often used for corrections.
  2. \t – Tab (U+0009)
    Adds a horizontal tab, useful for aligning text or creating indents.
  3. \n – Linefeed (U+000A)
    Moves the text to a new line, commonly used to break lines in a string.
  4. \f – Form Feed (U+000C)
    Used for page breaks in older printers or systems but is rarely seen today.
  5. \r – Carriage Return (U+000D)
    Moves the cursor to the beginning of the line, often used in combination with \n for new lines on Windows (\r\n).
  6. \" – Quote (U+0022)
    Allows you to include double quotes (") inside a string without ending it.
  7. \\ – Backslash (U+005C)
    Lets you include a backslash in the string.
  8. \uXXXX – Unicode (U+XXXX)
    Represents a Unicode character using 4 hexadecimal digits. For example, \u00E9 is the Unicode for “é.”
  9. \UXXXXXXXX – Unicode (U+XXXXXXXX)
    Represents a Unicode character using 8 hexadecimal digits, suitable for higher Unicode scalar values.

Key Notes:

  • Any escape sequences not listed above are reserved, and using them should result in an error in TOML.
  • Any Unicode character can be escaped using the \uXXXX or \UXXXXXXXX forms, as long as the code is a valid Unicode scalar value. For example, \u0041 represents “A”.

By using these escape sequences, you can handle formatting, symbols, and special characters more effectively in TOML strings, keeping your configuration files both clear and versatile.

Multiline Basic Strings

TOML makes it convenient to work with long passages of text or multi-line strings by using multi-line basic strings. These strings are enclosed with three double quotation marks (""") on each side. Here’s how it works:

Allowing Newlines:Multi-line basic strings allow text to span multiple lines, making them ideal for storing long passages (like translations or formatted text).

TOML
str1 = """
Roses are red
Violets are blue"""

Trimming Leading Newline:
If there’s a newline immediately after the opening """, it will be trimmed, ensuring the text starts neatly.

Whitespace and Newline Handling:
Any other whitespace or newline characters within the text are preserved, allowing for proper formatting.

Platform-Specific Newline Normalization:
TOML parsers may normalize newlines (\n for Unix-like systems or \r\n for Windows) depending on the platform.

TOML
# On a Unix system, the above multi-line string will most likely be the same as:
str2 = "Roses are red\nViolets are blue"

# On a Windows system, it will most likely be equivalent to:
str3 = "Roses are red\r\nViolets are blue"

Line Ending Backslash (\):
If you want to write long strings without adding extra spaces or newlines, you can use a line ending backslash (\). When a line ends with an unescaped \, it removes any following spaces, newlines, or tabs until the next visible character or closing delimiter.

The following strings are identical in value,

TOML
# The following strings are byte-for-byte equivalent:
str1 = "The quick brown fox jumps over the lazy dog."

str2 = """
The quick brown \


  fox jumps over \
    the lazy dog."""

str3 = """\
       The quick brown \
       fox jumps over \
       the lazy dog.\
       """

So, what does the rule say?
Any Unicode character is allowed, except those that require escaping, such as the backslash (\) itself and certain control characters other than tab, line feed, and carriage return (U+0000 to U+0008, U+000B, U+000C, U+000E to U+001F, U+007F).

There is another rule about quotation marks: You can use either single or double quotation marks anywhere inside a multi-line string.

Let’s explore this in depth with examples and explanations.

How Quotation Marks Work in Multi-Line Basic Strings

In TOML, multi-line basic strings use triple double quotes (""") to allow text to span multiple lines. However, using multiple consecutive quotation marks (") inside them can be tricky because it may conflict with the string delimiters. Let’s go through each example carefully.

Valid Example

TOML
str = """Here are two quotation marks: "". Simple enough."""

Here,

  • The string is enclosed in triple double quotes ("""), so it’s a valid multi-line string.
  • Inside the string, "" (two quotation marks) appear normally without causing any issues.

Invalid Example (incorrect syntax)

TOML
# str = """Here are three quotation marks: """."""  

Why is this invalid?

  • The first triple double quote (""") starts the string.
  • The next three quotes (""") end the string too early.
  • Then, TOML sees ."""" as invalid syntax outside the string.

Solution: Use an escape sequence to avoid closing the string unintentionally.

Corrected Example (escaping quotes)

TOML
str = """Here are three quotation marks: ""\"."""

Here,

  • The first two quotes "" appear normally.
  • The third quote \" is escaped, preventing the string from closing too early.
  • The final """ properly ends the string.

Including Many Quotes (Escaping for Clarity)

TOML
str = """Here are fifteen quotation marks: ""\"""\"""\"""\"""\"."""

Here,

  • Inside the string, we want to include """"""""""""" (15 double quotes).
  • To avoid accidentally ending the string, we escape every third quotation mark (\").
  • This allows TOML to correctly parse the string while still displaying 15 quotes inside it.

Quotes Inside a Sentence

TOML
str = """"This," she said, "is just a pointless statement.""""

As you see here,

  • The string starts and ends with triple double quotes ("""), which allow multi-line text.
  • The inner text contains regular double quotes ("This," and "is just a pointless statement."), which do not conflict with the string delimiters.
  • Since the first character inside the string is a quotation mark ("), it does not close the string because TOML allows this.

Alternative: Using Literal Strings to Avoid Escaping

If escaping feels complicated, TOML also supports literal strings, which use triple single quotes ('''). These do not process escape sequences, meaning you can write anything inside without worrying about backslashes or quote conflicts.

TOML
str_literal = '''Here are three quotation marks: """.'''

Why is this easier?

  • No need to escape quotes (\)—everything is taken as is.
  • Ideal for Windows paths, regex patterns, or text with many quotes.

Literal Strings

Single-Line Literal Strings

  • Surrounded by single quotes (').
  • No escaping (\) is allowed—everything inside is taken as it is.
  • Must be on a single line (no multi-line support).
TOML
# What you type is exactly what you get.
winpath  = 'C:\Users\nodejs\templates'  
winpath2 = '\\ServerX\admin$\system32\'  
quoted   = 'Amol "J" Pawar'  
regex    = '<\i\c*\s*>'

No need to escape backslashes (\) or quotes ("), making it useful for file paths and regex patterns.

Though it looks impressive, there is one limitation. Let’s see what it is and find a solution for it.

Limitation: Single Quote (') Cannot Be Used Inside

Since escaping is not allowed, you cannot include a single quote (') inside a single-quoted string.

This is NOT possible:

TOML
bad_example = 'I'm using TOML'  # INVALID because of the `'` in "I'm"

The workaround for this is to use multi-line literal strings. Let’s see it in detail.

Multi-Line Literal Strings (''')

  • Surrounded by triple single quotes (''').
  • No escaping is needed, and backslashes (\) are taken as it is.
  • Supports multiple lines while preserving whitespace and newlines.
  • The first newline after ''' is trimmed (ignored).
TOML
regex2 = '''I [dw]on't need \d{2} apples'''  
lines  = '''
The first newline is  
trimmed in raw strings.  
   All other whitespace  
   is preserved.  
'''

Again, why is this useful?

  • It allows single quotes (') inside the string, unlike single-line literal strings.
  • Also, whitespace and newlines are preserved exactly as they appear.

Limitation: Three Consecutive Single Quotes (''') Are Not Allowed

A sequence of three or more single quotes (''') inside a multi-line string is not permitted because it would conflict with the string delimiters.

This is NOT valid:

TOML
# INVALID because `''''''''''''''` is inside a multi-line literal string

apos15 = '''Here are fifteen apostrophes: ''''''''''''''''''  

Workaround: Use a double-quoted basic string ("""), which allows escaping:

TOML
apos15 = "Here are fifteen apostrophes: '''''''''''''''"

Handling Quotes in Multi-Line Strings

You can include one or two single quotes ('), but not three or more in a row.

TOML
quot15 = '''Here are fifteen quotation marks: """""""""""""""'''  

str = ''''That,' she said, 'is still pointless.''''

The first and last quote (') are just part of the string—they don’t interfere.

Control Characters Are Not Allowed

TOML does not allow control characters (except for tab).

  • This means you can’t store binary data directly in a literal string.
  • Use Base64 or another encoding if needed.

Conclusion

TOML strings offer a flexible and straightforward way to manage textual data in configuration files. By understanding the different types of strings and their use cases, you can write clean and maintainable TOML files that suit your application’s needs. With this guide, you now have the knowledge to effectively utilize TOML strings in your projects.

Toml Keys

Understanding TOML Keys: A Comprehensive Guide

In the world of configuration files, TOML (Tom’s Obvious Minimal Language) has emerged as a popular format for its simplicity, readability, and human-friendly syntax. TOML is widely used in modern programming ecosystems, including Rust and Python, where readability and maintainability are critical. In this blog, we’ll dive deep into TOML keys, understanding their syntax, usage, and nuances, with examples to make it crystal clear.

What Are TOML Keys?

In TOML, keys are used to identify values in a key-value pair. They are essential building blocks of TOML configuration files, much like variables in programming. Keys in TOML can be unquoted strings, quoted strings, or dotted paths.

Syntax of a Key-Value Pair

A basic key-value pair in TOML looks like this:

TOML
key = "value"

Here,

  • key is the name (identifier).
  • value is the corresponding value assigned to that key.

Types of Keys in TOML

There are three types of keys: bare, quoted, and dotted.

Bare keys

When you’re using bare keys (keys without quotes) in a file like TOML (a configuration file format), there are specific rules for naming them:

1. You can only use:

  • English letters (A-Z, a-z)
  • Numbers (0–9)
  • Underscores (_)
  • Dashes (-)

2. Bare keys can be made up of just numbers (like 1234), but even in that case, they will be treated as text (a string), not a number.

3. A bare key (a key without quotes) must not be empty — it has to contain at least one letter, number, underscore, or dash.

TOML
# valid bare keys
 
key = "value"
bare_key = "value"  # Valid, uses letters and underscore
bare-key = "value"  # Valid, uses letters and a dash
1234 = "value"      # Valid, but 1234 is treated as a string


# invalid bare keys

= "value"   # as we see here no key name is used, so bare key can't be empty

Quoted keys

Quoted keys are keys that are enclosed in quotes (either single ' or double " quotes). They can follow the same rules as basic strings or literal strings. This means you can use a wider variety of characters for the key name, like spaces or special symbols, which are not allowed in bare keys.

TOML
"key with space" = "value"  # Valid, space allowed in quoted key
"key@#%" = "value"         # Valid, special characters allowed

Best practice: It’s usually better to use bare keys (without quotes) because they are simpler and more common. Use quoted keys only when you really need them, like when your key has special characters that bare keys can’t handle.

Also, as we know, bare key must not be empty, However, we can have an empty quoted key (a key with quotes), but it’s not recommended to do so.

For example, this is valid but not ideal:

TOML
"" = "value"   # Empty quoted key is allowed but discouraged

'' = 'blank'     # again VALID but discouraged

So, while empty quoted keys are allowed, it’s better to avoid them if possible!

Note: Bare keys and quoted keys essentially mean the same thing—they’re just two different ways to represent the same key. However, we can’t use both in the same place. It needs to be either one or the other, but not both. For example, this won’t work:

TOML
# THIS WILL NOT WORK
spelling = "favorite"
"spelling" = "favourite"

Dotted keys

Dotted keys allow you to organize properties into groups by separating them with a dot (.). You can use either bare or quoted keys, and it helps structure related data, like categories or sub-properties.

TOML
name = "Orange"                  # A single key
physical.color = "orange"        # Grouped under 'physical'
physical.shape = "round"         # Another property under 'physical'

site."softaai.com" = true         # A key with a dot in it (quoted)

For better understanding, this is how it would look in JSON:

JSON
{
  "name": "Orange",
  "physical": {
    "color": "orange",
    "shape": "round"
  },
  "site": {
    "softaai.com": true
  }
}

Whitespace rules:

  • Whitespace around the dots is ignored. However, it’s better to avoid extra spaces for clarity.
  • Indentation (spaces at the beginning of a line) doesn’t matter and is ignored.
  • These are all treated the same:
TOML
fruit.name = "banana"    # Best practice
fruit. color = "yellow"  # Same as fruit.color (extra space)
fruit . flavor = "banana" # Same as fruit.flavor (extra space)

Ordering rule:

While it’s technically valid to define dotted keys out-of-order, it’s discouraged because it can make your code harder to read and understand. When you define the properties of related items (like apple and orange), it’s better to keep them grouped together.

Valid but discouraged:

TOML
apple.type = "fruit"
orange.type = "fruit"

apple.skin = "thin"
orange.skin = "thick"

apple.color = "red"
orange.color = "orange"

Recommended (better approach):

TOML
apple.type = "fruit"
apple.skin = "thin"
apple.color = "red"

orange.type = "fruit"
orange.skin = "thick"
orange.color = "orange"

Dotted key & Table Rule: 

Before going into the rules, understanding the table in TOML is important. We will look at the table in detail in the respective section, but for now, just understand the basics so that it will help in grasping the dotted key rules.

Tables are used to group related key-value pairs. There are two types of tables based on their usage: regular tables and nested tables.

  • Nested Tables: These tables use dot notation to organize hierarchical data.
  • Inline Tables: Mentioned briefly, inline tables are used for small data sets with a simpler syntax.
TOML
# Regular Table server
[server]
host = "127.0.0.1"
port = 8080

# Nested Table 
[server.logging]
level = "debug"
output = "stdout"

# Inline Table
user = { name = "amol", age = 28 }

Rule 1

In TOML, if a key hasn’t been defined yet, you can still create it as a table and add properties to it. When you use dotted keys, you’re essentially creating nested structures.

TOML
fruit.apple.smooth = true    # This creates a 'fruit' table with a sub-table 'apple', and inside that, 'smooth' as a key
fruit.orange = 2             # Adds a new key 'orange' to the 'fruit' table

However, if you define a key with a value (like a number), you can’t later treat it as a table.

For example, this won’t work:

TOML
fruit.apple = 1             # Sets 'apple' inside 'fruit' as an integer (not a table)
fruit.apple.smooth = true   # ERROR! Now 'apple' is an integer, not a table, so you can't add 'smooth' to it

In short, you can add to a table using dotted keys, but you can’t change a value into a table once it’s set.

Rule 2

You can technically use numbers in bare keys, which means you could create a dotted key that looks like a decimal or float number. For example:

TOML
3.14159 = "pi"

But, this isn’t actually a single key with a decimal point. Instead, TOML interprets this as two separate keys (3 and 14159) under a table structure. It would look like this in JSON:

JSON
{
  "3": { "14159": "pi" }
}

So, don’t use numbers like this unless you really need it, because it can be confusing. It’s better to avoid this and just use standard naming for your keys..!

Best Practices for Working with TOML Keys

Choose Descriptive Key Names
Use meaningful names to make your configuration file self-explanatory. 

For example,

TOML
[server]
host = "localhost"
port = 8080

Use Dotted Keys for Flat Structures
When possible, use dotted keys to avoid overcomplicating your configurations.

TOML
server.host = "127.0.0.1"

Overusing Dotted Keys While dotted keys are convenient, excessive nesting can reduce readability.

Use Quoted Keys When Necessary
Avoid bare keys if your keys contain spaces or special characters.

TOML
"db-host" = "localhost"

Maintain Consistency
Stick to a single convention for naming and structuring keys to ensure your TOML files remain clean and consistent.

Conclusion

TOML keys form the backbone of TOML configuration files. Whether you’re dealing with simple key-value pairs, hierarchical structures, or arrays of tables, understanding the types of keys and their proper usage ensures that your configuration files remain clean, readable, and maintainable. By following best practices and avoiding common pitfalls, you can harness the full potential of TOML for your projects.

With its human-readable syntax and powerful features, TOML continues to be a favorite choice for developers seeking simplicity and clarity in their configuration files.

Toml Intro

Understanding TOML: A Beginner’s Guide to a Minimal Yet Powerful Configuration Language

When it comes to configuration files, simplicity and clarity are essential. TOML (Tom’s Obvious, Minimal Language) is a configuration file format built to be easy to read, understand, and use. It’s lightweight, intuitive, and designed with clear structure, making it an excellent choice for managing settings in a variety of applications. In this blog, we’ll explore why TOML stands out and how it ensures efficient, error-free configuration handling.

What is TOML?

TOML is a data serialization language primarily used for configuration files. It was created by Tom Preston-Werner, co-founder of GitHub. It’s created with the goal of being minimal, meaning it doesn’t have unnecessary complexity or confusing syntax. The key design goals are:

  • Readability: It uses a simple, intuitive syntax that’s easy for humans to read and write. It mirrors common formats like INI but offers more structure..
  • Minimalism: It keeps the syntax simple while supporting complex data types.
  • Interoperability: It is easy to parse into various programming languages.

TOML files typically have a .toml extension and are widely used in projects like Rust’s Cargo and Python’s Poetry. Nowadays, it is also used in Android projects for Gradle version catalogs.

Minimalist and Easy-to-Read Format

TOML stands for Tom’s Obvious, Minimal Language, which gives you an idea of its core principle: keep things simple and clear. It was designed to minimize unnecessary syntax, which means it won’t overwhelm you with complex rules. Each piece of data is laid out clearly so that it’s easy for anyone to read and understand.

What makes TOML files so easy to read? One of its key features is its obvious semantics. When you look at a TOML file, it’s easy to figure out what each part means without much effort. Take this simple example.

TOML
host = "localhost"
port = 8080

Here, host is the key, and "localhost" is its value. Similarly, port is the key, and 8080 is the value. There’s no ambiguity — just a straightforward, easily interpretable configuration. Even if you’re not a developer, the structure is clear enough to understand what the data represents.

Clear Mapping to Hash Tables

One of the major design goals of TOML is that it maps naturally to a hash table. In programming, a hash table is a data structure that stores data in key-value pairs. This structure makes it easy to retrieve values associated with specific keys.

In TOML, each line is essentially a key-value pair, which makes it ideal for converting into a hash table. When a TOML file is read by a program, the key-value pairs are mapped into a data structure like a dictionary or map. For example:

TOML
host = "localhost"
port = 8080

This could be translated into a dictionary like this in Python:

Python
config = {"host": "localhost", "port": 8080}

This mapping allows for easy access to the data programmatically — you can quickly retrieve the value for a key (like host) without any confusion or overhead.

Cross-Language Parsing

TOML’s simplicity doesn’t just make it easy to read, it also ensures that parsing the file into usable data structures is straightforward in a wide variety of programming languages. It is designed to be compatible with many languages, including Python, Go, JavaScript, Rust, and more.

When you parse a TOML file in a program, it’s typically converted into a data structure like a map, dictionary, or object. This makes it easy to work with, regardless of the programming language. For example, in Python, a TOML file can be parsed into a dictionary, while in Go, it can be converted into a map.

This wide support means that It can be used in almost any environment(even in Kotlin also, I’ll get to that point later. First, let’s cover the basics), making it a versatile choice for managing configuration data.

Syntax Overview

TOML’s syntax is clean and minimal. Let’s explore its key components:

Key-Value Pairs

The core of TOML is the key-value pair. Keys represent names, and values store the associated data.

TOML
name = "amol pawar"
age = 28
is_developer = true
  • Keys: name, age, and is_developer.
  • Values: "amol pawar" (a string), 28 (an integer), and true (a boolean).

Important Note:

  • Keys are case-sensitive and can use letters, digits, underscores (_), and hyphens (-).
  • Strings are enclosed in double quotes ("), while numbers and booleans don’t require quotes.
  • You can use integers, floats, scientific notation, infinity, and NaN—all of which are supported.

Tables

Tables are used to group related key-value pairs.

TOML
[server]
host = "localhost"
port = 8080

Here, the [server] line creates a table named server. Also all subsequent key-value pairs (like server, port, etc.) belong to the server table.

Nested Tables

Tables can be nested using dot notation to organize hierarchical data.

TOML
[server]
host = "127.0.0.1"
port = 8080

[server.logging]
level = "debug"
output = "stdout"

Here, 

  • [server] is the parent table.
  • [server.logging] is a child table nested under server.
  • This structure is intuitive and mirrors nested dictionaries or objects in programming languages.

Inline Tables

For small tables, you can use inline syntax.

TOML
user = { name = "amol", age = 28 }

Have you noticed {}? Inline tables use curly braces ({}). They are ideal for concise, one-line representations of simple tables.

Arrays

TOML supports arrays for storing lists of values.

TOML
colors = ["red", "green", "blue"]
numbers = [1, 2, 3, 4, 5]
  • Arrays are enclosed in square brackets ([]).
  • Elements in an array must be of the same type (e.g., all strings or all numbers).

Arrays can also store inline tables:

TOML
servers = [
  { name = "alpha", ip = "192.168.1.1" },
  { name = "beta", ip = "192.168.1.2" }
]

Why Choose TOML?

So, why should we use TOML for our configuration files? Here are the main reasons:

  • Simplicity: TOML’s minimalistic design keeps things simple. You won’t find confusing syntax or complex features that complicate the file structure.
  • Readability: With its clear key-value pairs and logical structure, It is easy to read and edit. Anyone, even without a technical background, can easily understand what’s going on in the file.
  • Efficient Data Handling: The format’s design makes it easy to map the configuration data into hash tables, dictionaries, or maps, which are efficient to work with programmatically.
  • Cross-Language Compatibility: TOML can be easily parsed into data structures in various programming languages, which makes it a great option for projects that may span multiple languages.

Conclusion

TOML is a configuration file format designed for simplicity and clarity. Its minimalist design keeps configuration files easy to read and understand, while the mapping to hash tables makes it easy to work with programmatically. Whether you’re working on a small project or a large application, It provides an intuitive and reliable way to handle configuration settings. Its compatibility with multiple programming languages also ensures that it can be used across different environments with ease.

Compose Preview

Jetpack Compose Preview & Hilt Dependency Injection: Common Issues, Solutions, and Best Practices

In modern Android development, Jetpack Compose has simplified UI development, and Hilt has made dependency injection more streamlined. However, when working with Jetpack Compose Previews, you may encounter a common issue: Hilt dependency injection does not work in the context of Compose Previews.

In this detailed blog, we’ll break down the problem, explore why PreviewActivity doesn’t support Hilt, and show the best practices for managing dependencies during Compose Previews. We’ll also explore alternatives to using Hilt in previews while maintaining a smooth development experience.

What Are Compose Previews and Why Do We Use Them?

Before diving into the problem and solution, let’s first clarify what Compose Previews are and why they are useful:

  • Compose Previews allow developers to see their UI components directly in Android Studio without needing to run the entire app on a device or emulator.
  • Previews are a design-time tool for visualizing how your Composables will look under different states, layouts, and conditions.
  • The goal is to quickly iterate on your UI, test multiple configurations (like different themes, device sizes), and make changes to the UI without running the full app.

Compose Preview Limitations

While Compose Previews are powerful, they have some limitations:

  • Hilt Injection is Not Supported: By design, Hilt requires a dependency graph that is only created during runtime. Since Compose Previews are rendered before the app starts, there is no running application context where Hilt can inject dependencies.
  • No ViewModel Injection: Since Previews don’t have the full Android lifecycle, they also don’t support @HiltViewModel or other lifecycle-dependent mechanisms.

The Problem: PreviewActivity and Hilt

What is PreviewActivity?

  • PreviewActivity is a special activity used by Android Studio’s tooling to render Compose Previews.
  • It is part of the Compose Tooling and does not run as part of your actual application.
  • Since Hilt dependency injection relies on the app’s runtime environment to manage dependency graphs, and PreviewActivity does not have access to that runtime context, it cannot inject dependencies using Hilt.

Why the Error Occurs:

When you try to use Hilt in a preview and attempt to inject dependencies (such as ViewModels or services), Hilt encounters the following issue:

“Given component holder class androidx.compose.ui.tooling.PreviewActivity does not implement interface dagger.hilt.internal.GeneratedComponent

This error arises because PreviewActivity is not part of the app’s dependency graph, and Hilt cannot find any components to inject during preview rendering.

How to Handle Dependency Injection in Compose Previews

Now that we understand the problem, let’s look at the best practices for working around this limitation. Since Hilt cannot be used directly in Compose Previews, we will focus on methods that allow you to test your UI components effectively without Hilt.

Best Practice 1: Use Mock Dependencies

The most effective way to handle dependencies in Compose Previews is to use mock data or mock dependencies instead of relying on real Hilt dependencies. Since Compose Previews are for UI visualization and not real runtime behavior, mocking allows you to bypass the need for Hilt.

Mocking Dependencies in Previews

1. Create Mock Dependencies: For each service, ViewModel, or data source you need in your Composables, create a mock or simplified version of it.

Kotlin
class MockViewModel : ViewModel() {
    val sampleData = "Mock data for preview"
}

2. Use the Mock in Your Composable: When writing your Composables, pass the mocked data or services to the composable function.

Kotlin
@Composable
fun MyComposable(viewModel: MockViewModel) {
    Text(text = viewModel.sampleData)
}

3. Use @Preview with Mock Data: In the Preview function, instantiate the mock data directly.

Kotlin
@Preview(showBackground = true)
@Composable
fun MyComposablePreview() {
    MyComposable(viewModel = MockViewModel()) // Use mock data
}

Here,

  • Previews are meant for UI design, not for running real business logic or testing interactions.
  • By passing mock data, you can visualize the UI without needing real data or services.
  • This approach keeps previews lightweight and fast.

Best Practice 2: Use Default Arguments for Dependencies

Another approach is to use default arguments for dependencies in your Composables. This way, you can make sure that your Composables work both in the preview environment (with mock data) and in the app’s runtime (with Hilt-injected dependencies).

Default Arguments for Dependencies

1. Update Your Composables: Modify your Composables to use default arguments where appropriate.

Kotlin
@Composable
fun MyComposable(viewModel: MyViewModel = MockViewModel()) {
    Text(text = viewModel.sampleData)
}

2. Use @Preview with the Default Argument: In the Preview function, you don’t need to provide any dependencies explicitly because the MockViewModel will be used by default

Kotlin
@Preview(showBackground = true)
@Composable
fun MyComposablePreview() {
    MyComposable() // Use the default (mock) ViewModel
}

Here,

  • You can keep the same Composable for both Preview and Runtime by passing mock dependencies for previews.
  • In runtime, Hilt will inject the real ViewModel.

Best Practice 3: Use a Conditional DI Approach

If you are working with dependencies that are required for runtime but should be mocked in the preview, you can use a conditional DI approach where you check if you’re in the preview mode and inject mock data accordingly.

Kotlin
@Composable
fun MyComposable(viewModel: MyViewModel = if (BuildConfig.DEBUG) MockViewModel() else viewModel()) {
    Text(text = viewModel.sampleData)
}

@Preview
@Composable
fun MyComposablePreview() {
    MyComposable() // Will use MockViewModel in Preview
}

Best Practice 4: Avoid Hilt in Previews Entirely

Another strategy is to decouple your ViewModels or services from Hilt for the purposes of previews. This can be done by using interfaces or abstract classes for dependencies, which can then be mocked for preview.

Kotlin
interface DataProvider {
    fun getData(): String
}

class RealDataProvider : DataProvider {
    override fun getData(): String {
        return "Real Data"
    }
}

class MockDataProvider : DataProvider {
    override fun getData(): String {
        return "Mock Data for Preview"
    }
}

@Composable
fun MyComposable(dataProvider: DataProvider) {
    Text(text = dataProvider.getData())
}

@Preview(showBackground = true)
@Composable
fun MyComposablePreview() {
    MyComposable(dataProvider = MockDataProvider()) // Use mock provider for preview and for actual real provider
}

The last two approaches are quite self-explanatory, so I’ll skip further explanation and insights. Let’s directly jump to the final conclusion.

Conclusion

Hilt cannot be used in Jetpack Compose Previews because Previews don’t have access to the runtime dependency graph that Hilt creates. To work around this limitation, you can:

  1. Use Mock Dependencies: Simplify your Composables to accept mock data or services for previews.
  2. Use Default Arguments: Make your dependencies optional, allowing mock data to be injected in previews.
  3. Conditional Dependency Injection: Use a flag to determine whether to use mock data or real dependencies.
  4. Decouple Hilt Dependencies: Abstract dependencies behind interfaces so they can be easily mocked during previews.

By following these best practices, you can effectively handle dependencies in Compose Previews without running into issues with Hilt.

happy UI composeing..!

Hilt

Basic Internal Working of Hilt Dependency Injection in Android: Key Concepts Explained

Dependency Injection (DI) is a crucial concept in modern Android development, enabling better code maintainability, scalability, and testability. Hilt, built on top of Dagger, is the official dependency injection framework recommended by Google for Android. In this blog, we’ll dive deep into the internal workings of Hilt, exploring how it simplifies dependency injection, how it operates behind the scenes, and why it’s essential for building robust Android applications.

What is Dependency Injection?

Dependency Injection is a design pattern where an object’s dependencies are provided externally rather than the object creating them itself. This decouples object creation and object usage, making the code easier to test and manage.

Example without DI

Kotlin
class Engine {
    fun start() = "Engine started"
}

class Car {
    private val engine = Engine()
    fun drive() = engine.start()
}

Example with DI

Kotlin
class Car(private val engine: Engine) {
    fun drive() = engine.start()
}

Here, Engine is injected into Car, increasing flexibility and making it easier to swap or mock dependencies.

Why Hilt for Dependency Injection?

  • Simplifies the boilerplate code needed for dependency injection.
  • Manages dependency scopes automatically.
  • Integrates seamlessly with Jetpack libraries.
  • Provides compile-time validation for dependencies.

Internal Architecture of Hilt

At its core, Hilt builds upon Dagger 2, adding Android-specific integration and reducing boilerplate.

Key Components of Hilt

  1. @HiltAndroidApp: Annotates the Application class and triggers Hilt’s code generation.
  2. @AndroidEntryPoint: Used on Activities, Fragments, or Services to enable dependency injection.
  3. @Inject: Used to request dependencies in constructors or fields.
  4. @Module & @InstallIn: Define bindings and scope for dependencies.
  5. Scopes: @Singleton, @ActivityScoped, @ViewModelScoped, etc.

How Dependencies are Resolved

  • Hilt generates a component hierarchy based on annotations.
  • Dependencies are resolved from root components down to child components.
  • Each component manages its scoped dependencies.

Component Hierarchy in Hilt

Hilt creates several components internally:

  1. SingletonComponent: Application-wide dependencies.
  2. ActivityRetainedComponent: Survives configuration changes.
  3. ActivityComponent: Specific to Activity.
  4. FragmentComponent: Specific to Fragment.
  5. ViewModelComponent: Specific to ViewModel.

Flow of Dependency Resolution:

Kotlin
SingletonComponent  

ActivityRetainedComponent  

ActivityComponent  

FragmentComponent  

ViewModelComponent  

ViewComponent

Hilt’s Dual-Stage Approach

Hilt primarily provides dependencies at runtime, but it also performs compile-time validation and code generation to ensure correctness and optimize dependency injection.

Let’s see each approach in detail.

Compile-Time: Validation and Code Generation

Annotation Processing: Hilt uses annotation processors (kapt or ksp) during compile-time to scan annotations like @Inject, @HiltAndroidApp, @Module, @InstallIn, and others.

Dagger Code Generation: Hilt builds on top of Dagger, which generates code for dependency injection at compile-time. When annotations like @HiltAndroidApp, @Inject, and @Module are used, Hilt generates the required Dagger components and modules at compile-time.

Validation: 

Hilt ensures at compile-time that:

  • Dependencies have a valid scope (SingletonComponent, ActivityComponent, etc.).
  • Required dependencies are provided in modules.
  • There are no circular dependencies.

This means many potential runtime issues (like missing dependencies or incorrect scopes) are caught early at compile-time.

Note: At compile-time, Hilt generates the dependency graph and the necessary code for injecting dependencies correctly.

Run-Time: Dependency Provision and Injection

  • Once the code is compiled and ready to run, Hilt takes over at runtime to provide the actual dependency instances.
  • It uses the dependency graph generated at compile-time to resolve and instantiate dependencies.
  • Dependency injection happens dynamically at runtime using the generated Dagger components. For Example, a ViewModel with @Inject constructor gets its dependencies resolved and injected at runtime when the ViewModel is created.

Compile-Time vs Run-Time

So, while Hilt validates and generates code at compile-time, it provides and manages dependency instances at runtime.

If you’re looking for a one-liner:

“Hilt performs dependency graph generation and validation at compile-time, but the actual dependency provisioning happens at runtime.”

This dual-stage approach balances early error detection (compile-time) and flexibility in object creation (runtime).

Best Practices

  • Use @Singleton for dependencies shared across the entire application.
  • Avoid injecting too many dependencies into a single class.
  • Structure modules based on feature scope.
  • Leverage @Binds instead of @Provides when possible.

Conclusion

Hilt simplifies dependency injection in Android by reducing boilerplate and offering seamless integration with Jetpack libraries. Understanding its internal architecture, component hierarchy, and generated code can significantly improve your development process and app performance.

happy UI composeing..!

Hilt’s

Hilt’s Dual-Stage Approach: Unlocking Effortless and Powerful Dependency Injection in Android

In modern Android development, managing dependencies efficiently is crucial for maintaining clean, modular, and testable code. Dependency injection (DI) has emerged as a powerful pattern to decouple components and promote reusable and maintainable software. However, the complexity of managing DI can grow rapidly as an app scales. This is where Hilt comes in — a framework designed to simplify DI by offering a robust, intuitive, and highly optimized approach.

One of the standout features of Hilt is Hilt’s Dual-Stage Approach to dependency injection, which combines the best of compile-time validation and runtime dependency resolution. In this blog, we’ll dive deep into this approach, breaking down the key concepts, advantages, and inner workings of Hilt’s DI system.

What is Hilt’s Dual-Stage Approach?

Hilt is a dependency injection library built on top of Dagger, Google’s popular DI framework. It simplifies DI in Android apps by automating much of the boilerplate code required for DI setup and providing a more streamlined and user-friendly API. Hilt is fully integrated into the Android ecosystem, offering built-in support for Android components like Activities, Fragments, ViewModels, and Services.

Hilt’s Dual-Stage Approach

Hilt’s DI mechanism can be thought of as a two-phase process:

  1. Compile-Time: Code Generation & Validation
  2. Run-Time: Dependency Provision & Injection

Let’s explore each phase in detail.

Compile-Time: Code Generation & Validation

How It Works

During the compile-time phase, Hilt leverages annotation processing to generate the required code for DI. Hilt scans the annotations on your classes, interfaces, and methods, such as @Inject, @HiltAndroidApp, @Module, and @InstallIn, to perform two key tasks:

1. Code Generation:

  • Hilt generates the necessary Dagger components and other code to inject dependencies into Android classes like Activities, Fragments, ViewModels, and more.
  • The generated code includes classes that define how dependencies should be resolved. These are essentially the building blocks of the dependency graph.
  • For example, when you use @Inject on a constructor, Hilt creates the necessary code to inject dependencies into that class.

2. Validation:

Hilt performs compile-time validation to ensure that your dependency setup is correct and consistent. It checks for things like:

  • Missing dependencies: If a required dependency isn’t provided, the compiler will generate an error.
  • Incorrect scopes: Hilt checks that dependencies are provided with the correct scopes (e.g., @Singleton, @ActivityRetained).
  • Circular dependencies: If two or more components are dependent on each other, causing a circular dependency, Hilt will alert you during compilation.

By catching these issues at compile-time, Hilt prevents potential runtime errors, ensuring that your DI setup is error-free before the app even runs.

Why Compile-Time Matters

  • Error Prevention: Compile-time validation ensures that your dependencies are correctly wired up and that common mistakes (like missing dependencies or circular dependencies) are caught early.
  • Performance Optimization: Since code generation and validation happen before the app is even run, you avoid the overhead of checking for errors during runtime. This leads to a more efficient application.
  • Faster Feedback: You get immediate feedback when something goes wrong during the DI setup, allowing you to address issues right in your development workflow rather than debugging elusive runtime problems.

Run-Time: Dependency Provision & Injection

How It Works

At runtime, Hilt takes the generated dependency graph and uses it to inject dependencies into your Android components like Activities, Fragments, ViewModels, and Services. This is where the real magic happens — Hilt resolves all the dependencies and provides them to the components that need them.

Here’s a step-by-step explanation of how runtime DI works:

1. Dependency Resolution:

  • When an Android component (e.g., an Activity or ViewModel) is created, Hilt uses the dependency graph generated during compile-time to resolve and provide all the required dependencies.
  • For instance, if a ViewModel requires an API service, Hilt will resolve and inject an instance of that API service at runtime, ensuring that everything is ready for use.

2. Injection into Android Components:

  • Once the dependencies are resolved, Hilt injects them into your Android components using the generated code. The injection happens automatically when the component is instantiated, without you needing to manually call any dependency-provisioning methods.
  • For example, if you annotate a ViewModel with @HiltViewModel, Hilt will automatically inject the required dependencies into the ViewModel during its creation.

3. Lazy Injection & Scoping:

  • Hilt allows you to inject dependencies lazily using @Inject and @Lazy, ensuring that they are only created when needed. This improves performance by deferring the instantiation of expensive dependencies until they are required.
  • Hilt also supports scoping, which means that dependencies can be shared within certain components. For instance, a dependency injected into a @Singleton scope will have a single instance shared across the app’s lifecycle, whereas a dependency in a @ActivityRetained scope will be shared across an activity and its associated fragments.

Why Run-Time Matters

  • Dynamic Dependency Resolution: At runtime, Hilt dynamically resolves dependencies and ensures that the correct instances are injected into your components. This gives you the flexibility to change dependencies or modify scopes without needing to alter your code manually.
  • Efficiency in Object Creation: By handling dependency injection at runtime, Hilt ensures that your app only creates the dependencies it actually needs at the right time, minimizing memory usage and improving performance.
  • Flexibility and Maintainability: Hilt’s runtime mechanism allows for greater flexibility in terms of managing dependency lifecycles. You can easily swap out implementations of dependencies without affecting the rest of your app’s code.

Hilt’s Dual-Stage Approach: Key Benefits

1. Error-Free Dependency Setup

Hilt’s compile-time validation ensures that common DI issues, such as missing dependencies and incorrect scopes, are detected before the app even runs. This leads to fewer runtime errors and a smoother development experience.

2. Performance Optimization

By performing code generation and validation at compile-time, Hilt minimizes the performance impact during runtime. This means that your app can resolve and inject dependencies quickly, without the overhead of error checking or unnecessary object creation.

3. Seamless and Automatic Dependency Injection

Hilt’s runtime mechanism simplifies the process of injecting dependencies into Android components, making it nearly invisible to developers. Once you set up your dependencies and annotate your components, Hilt takes care of the rest — no need to manually handle object creation or dependency management.

4. Maintainability and Scalability

With Hilt, managing dependencies is easy and scalable. As your app grows, Hilt can efficiently handle more dependencies and complex dependency graphs, while ensuring that everything remains organized and maintainable. The separation of concerns between compile-time validation and runtime injection keeps the system modular and easy to extend.

Conclusion

Hilt’s Dual-Stage Approach provides a powerful and efficient mechanism for dependency injection in Android apps. By combining compile-time validation and code generation with runtime dependency resolution, Hilt allows you to manage dependencies seamlessly, with minimal boilerplate and maximum flexibility.

Whether you’re building a small app or a large-scale Android solution, Hilt’s integration into the Android ecosystem, along with its user-friendly API and automatic injection system, makes it a must-have tool for modern Android development.

By embracing Hilt’s DI system, you’ll find your codebase becoming cleaner, more modular, and more testable, with a reduced risk of runtime errors and performance bottlenecks. If you haven’t already adopted Hilt in your projects, now is the perfect time to unlock the full potential of seamless dependency injection!

happy UI composeing..!

Recomposition

State and Recomposition in Jetpack Compose: A Comprehensive Guide

Jetpack Compose has revolutionized Android UI development by introducing a declarative UI paradigm. At its core, Compose relies on two fundamental concepts: State and Recomposition. These concepts are crucial for building efficient, responsive, and reactive user interfaces.

What is State in Jetpack Compose?

State is any value that can change over time and affect the UI. In the context of UI development:

  • State represents any piece of data that can change over time and impacts the UI.
  • For example: A counter value in a button click scenario, a text field’s current input, or the visibility of a UI component( something like loading spinner).
  • Example:
Kotlin
var count by remember { mutableStateOf(0) }

I have a question: Is State quite similar to a regular Kotlin variable?

To answer this, we first need to understand what recomposition is and how it works. Once we understand that, we’ll be able to see the difference between state and regular Kotlin variables.

So, What is recomposition?

In Jetpack Compose, we build apps using hierarchies of composable functions. Each composable function takes data as input and uses it to create parts of the user interface, which are then displayed on the screen by the Compose runtime system.

Typically, the data passed between composable functions comes from a state variable declared in a parent function. If the state value changes in the parent, the child composables relying on that state must also reflect the updated value. Compose handles this with a process called recomposition.

Recomposition happens whenever a state value changes. Compose detects this change, identifies the affected composable functions, and calls them again with the updated state value.

Why is recomposition efficient?

Instead of rebuilding the entire UI tree, Compose uses intelligent recomposition. This means only the functions that actually use the changed state value are recomposed. This approach ensures that updates are fast and efficient, avoiding unnecessary processing.

In short, Compose efficiently updates only the parts of the UI affected by state changes, keeping performance smooth and responsive.

Now, back to our question: What’s the difference between a state variable and a regular Kotlin variable?

At first glance, a state variable in Jetpack Compose might seem similar to a regular Kotlin variable, which can also hold changing values during an app’s execution. However, state differs from a regular variable in two important ways:

  1. Remembering the state variable value: When you use a state variable inside a composable function (a UI function in Jetpack Compose), its value should be remembered between function calls. This means that the state doesn’t get reset every time the composable function is called again. If it were a normal variable, it would get reset each time the function is called, but for state variables, Jetpack Compose makes sure they “remember” their previous values so the UI stays consistent.
  2. Implications of changing a state variable: When you change the value of a state variable, it triggers a rebuild of not just the composable function where the state is defined, but also all the composable functions that depend on that state. This means that the entire UI tree (the hierarchy of composables) can be affected, and parts of it may need to be redrawn to reflect the new state. This is different from a regular function where changes in local variables only affect that specific function.

Let’s compare how state behaves differently from a regular Kotlin variable in code.

Kotlin
@Composable
fun Counter() {
    // State variable that remembers its value
    var count by remember { mutableStateOf(0) }

    Button(onClick = { count++ }) {
        Text("Clicked $count times")
    }
}

Here,

  • count is a state variable. Its value is “remembered” across recompositions.
  • When count changes (on clicking the button), the UI updates automatically.

On the other hand, with a regular Kotlin variable…

Kotlin
@Composable
fun Counter() {
    // Regular Kotlin variable
    var count = 0

    Button(onClick = { count++ }) {
        Text("Clicked $count times")
    }
}

Here,

  • count is a regular Kotlin variable.
  • When the button is clicked, count is incremented, but it doesn’t “remember” its previous value.
  • As a result, the composable doesn’t re-render with the updated value because the value of count is reset every time the composable is called.

In short: State in composables is “sticky” (it remembers its value), and changing the state affects the UI by causing relevant parts to be updated automatically. These two differences make state a powerful tool for managing dynamic and reactive UIs in Compose.

Mutable vs Immutable State

  • Mutable State: Can change over time (e.g., mutableStateOf)
  • Immutable State: Cannot change once initialized (e.g., val)

Why is State Important?

State drives how your UI looks and behaves. In Compose:

  • State updates trigger recompositions (UI updates).
  • Compose ensures the UI is always consistent with the current state.

Conclusion

State and Recomposition are foundational concepts in Jetpack Compose. Properly managing state and understanding how recomposition works can lead to efficient and scalable Android applications.

Understanding these concepts is not just about syntax but about building a mental model for how Jetpack Compose interacts with state changes. Start experimenting with small examples, and you’ll soon master the art of managing state in Compose effectively.

error: Content is protected !!