Thanks for the explanation, as I get older I find it harder to grasp new things, so has chatgpt explain it to me like a kindergartener, so here's what I got
Understanding the New B4X Optional Sub Parameters—with Practical Examples
B4X is introducing support for
optional Sub parameters. This feature allows us to declare parameters with default values so callers do not have to supply every parameter every time a Sub is called.
In simple terms, an optional parameter tells the compiler:
“If the caller supplies this value, use it. Otherwise, use the default value declared in the Sub.”
This can make our code shorter, easier to read, easier to maintain, and much easier to extend without breaking existing calls.
The original announcement can be found here:
https://www.b4x.com/android/forum/threads/b4x-optional-sub-parameters.171906/'][B4X] Optional Sub Parameters[/URL]
1. A simple example
Consider the following Sub:
Private Sub ShowMessage(Text As String, Title As String, Duration As Int)
Log(Title & ": " & Text)
End Sub
Every time we call it, all three parameters must be provided:
ShowMessage("Connection successful", "Bluetooth", 2000)
This is fine, but imagine that most messages use the same title and duration. We would repeatedly pass the same values throughout the application.
With optional parameters, the Sub can be declared like this:
Private Sub ShowMessage(Text As String, _
Title As String = "Information", _
Duration As Int = 2000)
Log(Title & ": " & Text)
End Sub
Now it can be called with only the required parameter:
ShowMessage("Connection successful")
The compiler effectively treats this as:
ShowMessage("Connection successful", "Information", 2000)
We can override only the title:
ShowMessage("Connection successful", "Bluetooth")
The default duration of 2000 will still be used.
We can also override everything:
ShowMessage("Battery critically low", "Warning", 5000)
The common call remains short, while unusual cases can still be fully customized.
2. Optional parameter syntax
A parameter becomes optional when a default value is assigned to it in the Sub declaration:
Private Sub CalculatePrice(Price As Double, _
TaxRate As Double = 0.095, _
Discount As Double = 0) As Double
Return (Price - Discount) * (1 + TaxRate)
End Sub
It can then be called in several ways:
Dim Total1 As Double = CalculatePrice(100)
Dim Total2 As Double = CalculatePrice(100, 0.08)
Dim Total3 As Double = CalculatePrice(100, 0.08, 10)
These calls mean:
'Uses TaxRate = 0.095 and Discount = 0
Dim Total1 As Double = CalculatePrice(100)
'Uses TaxRate = 0.08 and Discount = 0
Dim Total2 As Double = CalculatePrice(100, 0.08)
'Uses TaxRate = 0.08 and Discount = 10
Dim Total3 As Double = CalculatePrice(100, 0.08, 10)
There is no separate
Optional keyword. Assigning a default value with
= is what makes the parameter optional.
3. Optional parameters must come last
All required parameters must appear before the optional parameters.
This is valid:
Private Sub SendCommand(Command As String, _
Timeout As Int = 5000, _
RetryCount As Int = 3)
End Sub
In this example:
[]Command is required.
[]Timeout is optional.
RetryCount is optional.
The following declaration would not be valid:
Private Sub SendCommand(Command As String, _
Timeout As Int = 5000, _
DeviceId As String)
End Sub
The problem is that
DeviceId is required but appears after an optional parameter.
The basic rule is:
Required parameters first, optional parameters last.
Once a parameter has a default value, all parameters following it must also have default values.
4. Omitting trailing parameters
Suppose we define:
Private Sub SendCommand(Command As String, _
Timeout As Int = 5000, _
RetryCount As Int = 3, _
WriteLog As Boolean = True)
'Send the command...
End Sub
We can use the following calls:
SendCommand("MOVE")
SendCommand("MOVE", 10000)
SendCommand("MOVE", 10000, 5)
SendCommand("MOVE", 10000, 5, False)
Each call supplies parameters from left to right.
What we currently cannot do is skip a parameter in the middle:
'Currently not supported:
SendCommand("MOVE", , , False)
That imaginary call would mean:
[]Use the default timeout.
[]Use the default retry count.
- Set WriteLog to False.
According to the announcement discussion, skipping middle parameters is not currently supported.
Optional parameters can therefore be omitted only from the end of the argument list.
This is important when designing a Sub. Parameters that callers will frequently override should generally appear before parameters that will rarely be changed.
For example, if
WriteLog is changed more frequently than the timeout, this order may be better:
Private Sub SendCommand(Command As String, _
WriteLog As Boolean = True, _
Timeout As Int = 5000, _
RetryCount As Int = 3)
'Send the command...
End Sub
Now logging can be disabled without specifying the timeout and retry count:
SendCommand("MOVE", False)
5. Real-life example: writing data to a BLE device
A BLE helper might require the following information:
[]Characteristic UUID
[]Data to write
[]Whether the write requires a response
[]Timeout
- Whether the packet should be logged
Without optional parameters, we might have:
Private Sub WriteCharacteristic(CharacteristicUUID As String, _
Data() As Byte, _
WithResponse As Boolean, _
TimeoutMs As Int, _
LogPacket As Boolean) As ResumableSub
'Perform BLE write...
Return True
End Sub
Every call must provide all five parameters:
Wait For (WriteCharacteristic(ChairMotionCMD_UUID, _
Array As Byte(0x01), True, 5000, True)) _
Complete (Success As Boolean)
However, most writes may use the same values:
[]WithResponse = True
[]TimeoutMs = 5000
LogPacket = True
With optional parameters:
Private Sub WriteCharacteristic(CharacteristicUUID As String, _
Data() As Byte, _
WithResponse As Boolean = True, _
TimeoutMs As Int = 5000, _
LogPacket As Boolean = True) As ResumableSub
If LogPacket Then
Log($"Writing ${Data.Length} bytes to ${CharacteristicUUID}"$)
End If
'Perform BLE write...
Return True
End Sub
The normal call becomes much cleaner:
Wait For (WriteCharacteristic(ChairMotionCMD_UUID, _
Array As Byte(0x01))) Complete (Success As Boolean)
For an unusual operation, all settings can still be supplied:
Wait For (WriteCharacteristic(ChairMotionCMD_UUID, _
Array As Byte(0x01), False, 10000, False)) _
Complete (Success As Boolean)
This is an excellent use of optional parameters: the common case remains simple while advanced cases remain configurable.
6. Real-life example: HTTP requests
An HTTP helper might originally look like this:
Private Sub SendApiRequest(Url As String, _
Method As String, _
Body As String, _
Headers As Map, _
Timeout As Int) As ResumableSub
Even a simple GET request would require several values that may not be relevant:
Wait For (SendApiRequest(ApiUrl, "GET", "", Null, 30000)) _
Complete (Response As String)
With optional parameters:
Private Sub SendApiRequest(Url As String, _
Method As String = "GET", _
Body As String = "", _
Headers As Map = Null, _
Timeout As Int = 30000) As ResumableSub
Dim Job As HttpJob
Job.Initialize("", Me)
Select Method.ToUpperCase
Case "GET"
Job.Download(Url)
Case "POST"
Job.PostString(Url, Body)
Case "PUT"
Job.PutString(Url, Body)
Case "DELETE"
Job.Delete(Url)
End Select
If Headers <> Null Then
For Each Key As String In Headers.Keys
Job.GetRequest.SetHeader(Key, Headers.Get(Key))
Next
End If
Job.GetRequest.Timeout = Timeout
Wait For (Job) JobDone(Job As HttpJob)
Dim Result As String = ""
If Job.Success Then
Result = Job.GetString
End If
Job.Release
Return Result
End Sub
A simple GET request becomes:
Wait For (SendApiRequest(ApiUrl)) Complete (Response As String)
A POST request can be:
Wait For (SendApiRequest(ApiUrl, "POST", JsonBody)) _
Complete (Response As String)
An advanced request can still provide all settings:
Dim Headers As Map = CreateMap( _
"Authorization": "Bearer " & Token, _
"Content-Type": "application/json")
Wait For (SendApiRequest(ApiUrl, "POST", JsonBody, Headers, 60000)) _
Complete (Response As String)
7. Real-life example: creating UI elements
A UI helper may contain many configuration parameters:
Private Sub CreateStyledButton(Text As String, _
Width As Int, _
Height As Int, _
BackgroundColor As Int, _
TextColor As Int, _
FontSize As Float) As B4XView
Every button would require a long call:
Dim ConnectButton As B4XView = CreateStyledButton( _
"Connect", 160dip, 50dip, 0xFF1976D2, _
xui.Color_White, 16)
With optional parameters:
Private Sub CreateStyledButton(Text As String, _
Width As Int = 160dip, _
Height As Int = 50dip, _
BackgroundColor As Int = 0xFF1976D2, _
TextColor As Int = 0xFFFFFFFF, _
FontSize As Float = 16) As B4XView
Dim Button As Button
Button.Initialize("Button")
Button.Text = Text
Dim View As B4XView = Button
View.SetLayoutAnimated(0, 0, 0, Width, Height)
View.Color = BackgroundColor
View.TextColor = TextColor
View.TextSize = FontSize
Return View
End Sub
The common case becomes:
Dim ConnectButton As B4XView = CreateStyledButton("Connect")
A customized button can still be created:
Dim DeleteButton As B4XView = CreateStyledButton( _
"Delete", 120dip, 45dip, 0xFFD32F2F)
8. Real-life example: database queries
The original announcement includes an example similar to this:
Public Sub ExecQuerySingleResult(Query As String, _
Args As List = Null, _
DefaultValue As Object = Null) As Object
A query without parameters can be called with:
Dim Count As Object = ExecQuerySingleResult( _
"SELECT COUNT(*) FROM Users")
A parameterized query can be called with:
Dim UserName As Object = ExecQuerySingleResult( _
"SELECT Name FROM Users WHERE UserId = ?", _
Array(UserId))
A default result can also be supplied:
Dim UserName As Object = ExecQuerySingleResult( _
"SELECT Name FROM Users WHERE UserId = ?", _
Array(UserId), _
"Unknown User")
Before optional parameters, a developer might create several similar methods:
ExecQuerySingleResult(Query)
ExecQuerySingleResult2(Query, Args)
ExecQuerySingleResultWithDefault(Query, Args, DefaultValue)
Optional parameters allow these variations to be represented by one cleaner API.
9. Adding new parameters without breaking existing code
One of the greatest benefits is the ability to extend a Sub without modifying all existing calls.
Imagine a project containing many calls to:
SaveMeasurement(UserId, Measurement)
Later, cloud synchronization is added:
Private Sub SaveMeasurement(UserId As String, _
Measurement As Map, _
SyncToCloud As Boolean)
Without optional parameters, every existing call must be updated:
SaveMeasurement(UserId, Measurement, True)
With an optional parameter:
Private Sub SaveMeasurement(UserId As String, _
Measurement As Map, _
SyncToCloud As Boolean = True)
'Save the measurement...
End Sub
All existing calls remain valid:
SaveMeasurement(UserId, Measurement)
Only the unusual case needs the additional argument:
SaveMeasurement(UserId, Measurement, False)
This will be particularly useful for:
[]Large applications
[]Shared code modules
[]Cross-platform B4X code
[]Reusable classes
[]Libraries
[]APIs that evolve over time
Shorter calls are convenient, but being able to extend an API without changing dozens or hundreds of existing calls may be the most valuable benefit.
10. Using Null as the default value
An optional object can default to
Null:
Private Sub ShowUser(UserData As Map = Null)
If UserData = Null Then
Log("No user was provided")
Return
End If
Log(UserData.Get("Name"))
End Sub
It can be called without an argument:
Or with a Map:
ShowUser(CreateMap("Name": "Walter"))
Using
Null is useful for optional objects such as:
[]Maps
[]Lists
[]Bitmaps
[]Callbacks
[]Configuration objects
[]Database arguments
However, omitting a parameter and explicitly passing
Null are not necessarily the same operation.
For example:
Private Sub Test(Value As Object = 10)
Log(Value)
End Sub
These calls are conceptually different:
In the first call, the compiler inserts the default value of 10.
In the second call, the caller explicitly supplies
Null.
The Sub should therefore define what an explicitly passed
Null means.
11. This is a compile-time feature
Optional parameters are handled by the compiler.
Given:
Private Sub LogEvent(Message As String, _
Level As String = "INFO")
Log(Level & ": " & Message)
End Sub
When we write:
LogEvent("Application started")
The compiler effectively treats the call as:
LogEvent("Application started", "INFO")
The Sub simply receives the final values. It does not need to determine whether the caller omitted a parameter.
Therefore, these calls are effectively equivalent:
LogEvent("Application started")
LogEvent("Application started", "INFO")
12. CallSub and dynamically called Subs
Because optional parameter handling occurs at compile time, dynamic calls using
CallSub still require the expected arguments.
For example:
Public Sub UpdateStatus(Status As String, _
Animate As Boolean = True)
'Update UI...
End Sub
A direct call can omit the second parameter:
UpdateStatus("Connected")
However, developers should not assume that a dynamically invoked Sub will automatically receive its optional defaults.
Dynamic calls such as
CallSub,
CallSub2, and
CallSub3 have their own fixed argument-count behavior.
Subs intended primarily for events, callbacks, or other dynamic invocation should therefore keep the required signature explicit.
13. Java library support
Optional parameters can also be exposed by Java libraries using the
@DefaultValue annotation:
public static String BytesToString(
byte[] data,
@DefaultValue("0") int startOffset,
@DefaultValue("-1") int length,
@DefaultValue("UTF-8") String charset) {
//Convert bytes to a string...
}
B4X code could then call it with only the byte array:
Dim Text As String = BytesToString(Data)
Or provide every setting:
Dim Text As String = BytesToString( _
Data, 0, Data.Length, "CP-1252")
The default value in the Java annotation is written as a string and parsed based on the parameter type when the library is loaded.
According to the announcement, expressions are not supported for defaults declared in Java or Objective-C libraries.
This should help library developers expose flexible APIs without requiring multiple nearly identical methods.
14. When optional parameters are a good choice
Optional parameters work well when:
[]There is an obvious and safe default value.
[]Most callers use the same value.
[]The parameter controls advanced or less frequently used behavior.
[]A new capability is being added to an existing Sub.
[]Several similar Subs differ only by one or two parameters.
[]Omitting the value does not make the operation ambiguous.
Good examples might include:
Connect(DeviceId, Timeout = 10000)
ShowNotification(Message, Sound = True, Duration = 3000)
SaveFile(Path, Data, Overwrite = False)
CalculateMeasurement(Landmarks, Scale = 1.0, _
ApplyCorrection = True)
SendEmail(Address, Subject, Body, IsHtml = False)
15. When optional parameters may not be appropriate
Optional parameters should not be used when omitting a value could hide an important programming mistake.
For example:
Private Sub TransferMoney(Account As String, _
Amount As Double = 1000)
End Sub
A caller could accidentally omit the amount and silently transfer $1,000.
That is less of a default value and more of a booby trap wearing a necktie.
A safer design would require both values:
Private Sub TransferMoney(Account As String, _
Amount As Double)
End Sub
It is also wise to avoid creating Subs with too many optional Boolean or configuration parameters:
CreateReport(Name, "PDF", True, False, True, _
False, True, False)
Even if the declaration uses optional parameters, a call containing a sequence such as:
is difficult to understand without looking at the Sub declaration.
At that point, an options type or configuration object may be clearer:
Type ReportOptions( _
Format As String, _
IncludeImages As Boolean, _
Compress As Boolean, _
SendEmail As Boolean, _
SaveToCloud As Boolean)
Example:
Dim Options As ReportOptions
Options.Initialize
Options.Format = "PDF"
Options.IncludeImages = True
Options.SendEmail = True
CreateReport("Monthly Report", Options)
A reasonable rule of thumb might be:
[]One to three optional parameters: usually clear and manageable.
[]Several Boolean configuration parameters: consider an options type.
[]Safety-critical values: make them required.
[]If a call contains a mysterious row of True and False values, the API may need to be redesigned.
Conclusion
Optional Sub parameters provide three major benefits.
1. Cleaner calls
Instead of:
Connect(DeviceId, 10000, True, 3)
We may be able to write:
2. Centralized default values
Default timeouts, retry counts, colors, logging settings, and other common values can be declared in one location instead of being repeated throughout the project.
3. Easier API evolution
An existing Sub:
can potentially be expanded without breaking its existing callers:
Private Sub SaveData(Data As Object, _
Compress As Boolean = False)
End Sub
The main points to remember are:
[]A parameter becomes optional when it is assigned a default value.
[]Required parameters must appear first.
[]Optional parameters must appear at the end.
[]Only trailing parameters can currently be omitted.
[]Middle parameters cannot currently be skipped.
[]Dynamic CallSub calls still require the appropriate arguments.
- Default values should be predictable and safe.
Overall, this is a very useful improvement to B4X.
It will make common calls shorter, reusable APIs cleaner, and existing projects easier to extend without modifying every call site.
It may not completely change the way we write B4X applications, but it should eliminate quite a bit of repetitive code—and fewer repetitive parameters means fewer opportunities to accidentally put
True,
False, or
5000 in the wrong position.