Android Question [B4X] Optional sub parameters

walterf25

Expert
Licensed User
Longtime User
Hi all, I love that B4X keeps coming up with new updates such as this Optional sub parameters, however I'm a little confused, maybe you guys can shed some light, what does this feature solve, how can it be used in a real life example?

Thanks guys!
 

teddybear

Well-Known Member
Licensed User
This feature is the icing on the cake. technically speaking, optional sub‑parameters are syntactic sugar. it makes the code look much cleaner and improves readability
 
Upvote 0

Erel

B4X founder
Staff member
Licensed User
Longtime User
Make sure to read: [B4X] Optional sub parameters

It helps design and provide cleaner APIs. It allows setting good defaults, without restricting the developer.

Take File.OpenOutput for example. Last parameter is the append flag. There are very few cases where this flag is set to True, so why force the developer to enter it each time.
It is also a much nicer alternative to multiple versions of the same method (DoSomething, DoSomething2(WithParameter)...).

Last but not least, in some cases it allows changing the API without breaking existing code.
 
Upvote 0

aeric

Expert
Licensed User
Longtime User
Imagine I have a sub for inserting or updating a row in database table.
B4X:
Sub Save (user_id As Int, user_name As String, user_age As Int, user_email As String, user_password As String, user_occupation As String)

End Sub
In the pass, I need to use Array, Map, custom type to create a cleaner sub with single parameter or leave empty Strings when call it. It looks ugly and may need more lines of code.
B4X:
Save(1, "John Cena", 21, "", "", "")
Now
B4X:
Save(1, "John Cena")

I think it is also useful for initializing values when create custom type.
B4X:
Type User (user_id As Int, user_name As String, user_age As Int, user_email As String, user_password As String, user_occupation As String)

Sub CreateUser(user_id As Int, user_name As String = "NoName", user_age As Int = 21, user_email As String = "", user_password As String = "default", user_occupation As String = "Actor")

End Sub

CreateUser (1, "John Cena")
 
Last edited:
Upvote 0

walterf25

Expert
Licensed User
Longtime User
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:

B4X:
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:

B4X:
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:

B4X:
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:

B4X:
ShowMessage("Connection successful")

The compiler effectively treats this as:

B4X:
ShowMessage("Connection successful", "Information", 2000)

We can override only the title:

B4X:
ShowMessage("Connection successful", "Bluetooth")

The default duration of 2000 will still be used.

We can also override everything:

B4X:
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:

B4X:
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:

B4X:
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:

B4X:
'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:

B4X:
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:

B4X:
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:

B4X:
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:

B4X:
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:

B4X:
'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:

B4X:
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:

B4X:
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:

B4X:
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:

B4X:
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:

B4X:
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:

B4X:
Wait For (WriteCharacteristic(ChairMotionCMD_UUID, _
Array As Byte(0x01))) Complete (Success As Boolean)

For an unusual operation, all settings can still be supplied:

B4X:
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:

B4X:
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:

B4X:
Wait For (SendApiRequest(ApiUrl, "GET", "", Null, 30000)) _
Complete (Response As String)

With optional parameters:

B4X:
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:

B4X:
Wait For (SendApiRequest(ApiUrl)) Complete (Response As String)

A POST request can be:

B4X:
Wait For (SendApiRequest(ApiUrl, "POST", JsonBody)) _
Complete (Response As String)

An advanced request can still provide all settings:

B4X:
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:

B4X:
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:

B4X:
Dim ConnectButton As B4XView = CreateStyledButton( _
"Connect", 160dip, 50dip, 0xFF1976D2, _
xui.Color_White, 16)

With optional parameters:

B4X:
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:

B4X:
Dim ConnectButton As B4XView = CreateStyledButton("Connect")

A customized button can still be created:

B4X:
Dim DeleteButton As B4XView = CreateStyledButton( _
"Delete", 120dip, 45dip, 0xFFD32F2F)

8. Real-life example: database queries

The original announcement includes an example similar to this:

B4X:
Public Sub ExecQuerySingleResult(Query As String, _
Args As List = Null, _
DefaultValue As Object = Null) As Object

A query without parameters can be called with:

B4X:
Dim Count As Object = ExecQuerySingleResult( _
"SELECT COUNT(*) FROM Users")

A parameterized query can be called with:

B4X:
Dim UserName As Object = ExecQuerySingleResult( _
"SELECT Name FROM Users WHERE UserId = ?", _
Array(UserId))

A default result can also be supplied:

B4X:
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:

B4X:
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:

B4X:
SaveMeasurement(UserId, Measurement)

Later, cloud synchronization is added:

B4X:
Private Sub SaveMeasurement(UserId As String, _
Measurement As Map, _
SyncToCloud As Boolean)

Without optional parameters, every existing call must be updated:

B4X:
SaveMeasurement(UserId, Measurement, True)

With an optional parameter:

B4X:
Private Sub SaveMeasurement(UserId As String, _
Measurement As Map, _
SyncToCloud As Boolean = True)

'Save the measurement...
End Sub

All existing calls remain valid:

B4X:
SaveMeasurement(UserId, Measurement)

Only the unusual case needs the additional argument:

B4X:
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:

B4X:
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:

B4X:
ShowUser

Or with a Map:

B4X:
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:

B4X:
Private Sub Test(Value As Object = 10)
Log(Value)
End Sub

These calls are conceptually different:

B4X:
Test
Test(Null)

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:

B4X:
Private Sub LogEvent(Message As String, _
Level As String = "INFO")

Log(Level & ": " & Message)
End Sub

When we write:

B4X:
LogEvent("Application started")

The compiler effectively treats the call as:

B4X:
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:

B4X:
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:

B4X:
Public Sub UpdateStatus(Status As String, _
Animate As Boolean = True)

'Update UI...
End Sub

A direct call can omit the second parameter:

B4X:
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:

Java:
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:

B4X:
Dim Text As String = BytesToString(Data)

Or provide every setting:

B4X:
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:

B4X:
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:

B4X:
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:

B4X:
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:

B4X:
CreateReport(Name, "PDF", True, False, True, _
False, True, False)

Even if the declaration uses optional parameters, a call containing a sequence such as:

B4X:
True, False, True, False

is difficult to understand without looking at the Sub declaration.

At that point, an options type or configuration object may be clearer:

B4X:
Type ReportOptions( _
Format As String, _
IncludeImages As Boolean, _
Compress As Boolean, _
SendEmail As Boolean, _
SaveToCloud As Boolean)

Example:

B4X:
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:

B4X:
Connect(DeviceId, 10000, True, 3)

We may be able to write:

B4X:
Connect(DeviceId)

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:

B4X:
SaveData(Data)

can potentially be expanded without breaking its existing callers:

B4X:
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.
 
Upvote 0

walterf25

Expert
Licensed User
Longtime User
Thanks I think I understand this better.
 
Upvote 0

ilan

Expert
Licensed User
Longtime User
really cool.
will it be possible to pass like 2 parameters but that are not following?


B4X:
sub anyfunction(num1 as int = 5, num2 as int =10, num3 as int =8)
log(num1+num2-num3)
end sub

B4X:
anyfucntion(2,5) 'LOG(7)'
 
Upvote 0

Erel

B4X founder
Staff member
Licensed User
Longtime User
will it be possible to pass like 2 parameters but that are not following?
No. It will be too confusing. How can the compiler / developer know which parameters are being set?

The Python solution for such cases is to use named arguments:
B4X:
anyfunction(num1=9, num3=20)

I don't think that it will be added to B4X.
 
Upvote 0

ilan

Expert
Licensed User
Longtime User
named arguments is the correct way or the only way i can think of making this possible is in events that have multiple types like: string, int, long,..

B4X:
sub anyfunction(num1 as int = 5, num2 as int =10, txt as string = "sum")
log(txt & (num1+num2))
end sub

B4X:
anyfucntion(2,"total ") 'LOG(total 12)'

 
Upvote 0

ilan

Expert
Licensed User
Longtime User
at least i think there needs to be like a way to add 2 parameters but keep a specific parameter with default value like:


B4X:
sub anyfunction(num1 as int = 5, num2 as int =10, txt as string = "sum")
log(txt & (num1+num2))
end sub

B4X:
anyfucntion(2,[def],"total ") 'LOG(total 12)'

if not it will only be possible to update the first parameter if you want to keep the others default

and the order of the structure must be very well thought when you create that function.

a syntex that tells the event to use the default parameter at that specific position will be a good addition
 
Upvote 0
Cookies are required to use this site. You must accept them to continue using the site. Learn more…