Real-time WebSocket chat¶
A chat screen asks three things of its connection: be open while the screen is
in use, hand each message to the interface, and come back by itself when the
network drops. websocket does all three, on the phone and in the Previewer,
with nothing to install.
A room in one file¶
from apkpy_lib import Screen, button, inputs, label, lifecycle, run, websocket
room = Screen(id="room")
status = label("Offline", id="status", screen=room)
latest = label("No messages yet", id="latest", screen=room)
message = inputs("Write a message", id="message", screen=room)
def connected():
status.set_value("Live")
def received(text):
latest.set_value(text)
def closed(code, reason):
status.set_value("Closed: " + reason)
def failed(error):
status.set_value("Reconnecting")
def connect_room():
websocket.connect(
"support-room",
"wss://api.example.com/chat",
headers={"Authorization": "Bearer YOUR_TOKEN"},
on_open=connected,
on_message=received,
on_close=closed,
on_error=failed,
reconnect=True,
)
def send_message():
text = message.get_value()
if text != "":
websocket.send("support-room", text)
message.set_value("")
def leave_room():
websocket.close("support-room")
button("Send", command=send_message, screen=room)
lifecycle(room, on_resume=connect_room, on_destroy=leave_room)
run(room)
What each part does¶
- The connection has a name.
"support-room"is howsend()andclose()find it. Connecting again with the same name replaces the earlier connection, so connecting inon_resumeis safe: coming back to the screen does not open a second socket. on_open()runs once the handshake is done.on_message(text)receives each text message. It runs on the interface thread, so it can change components straight away.on_close(code, reason)receives the close code and the reason, both as text.on_error(error)receives a description of what failed. Withreconnect=True, a new attempt follows.headers=travel with the upgrade request, which is where a token goes.protocols=lists the subprotocols the server may choose from.
On Android the connection is made with OkHttp, which the build adds only to
apps that call websocket.
Sending before the connection is open¶
send() can be called right after connect(). While the handshake runs, up to
64 messages wait in order and leave as soon as the connection opens. A 65th
message pushes out the oldest one, so a long outage cannot fill the phone's
memory with unsent text.
When the network drops¶
With reconnect=True, the default, a connection that fails or is cut tries
again by itself:
| Argument | Default | What it does |
|---|---|---|
reconnect_delay |
1.0 |
seconds before the first new attempt |
max_reconnect_delay |
15.0 |
the longest wait between attempts |
ping_interval |
25.0 |
seconds between pings that keep a quiet connection alive |
Each failed attempt doubles the wait, up to max_reconnect_delay, with a small
random margin so that many phones do not reconnect in the same second. A
connection that opens starts the count again. Only close() stops the attempts.
Mobile networks and home routers often drop connections that stay silent for a minute or two. The ping is what keeps a room that nobody is typing in from being closed under you.
Closing¶
websocket.close("support-room") closes with code 1000 and stops reconnection.
websocket.close("support-room", code=4000, reason="left") sends a code and a
reason of your own. Close in on_destroy, as above, or when the person leaves
the room.
Trying it before a build¶
The Previewer opens a real connection from your computer with the same calls, so a server on your machine is enough to try the screen, the reconnection and the messages sent during the handshake before building an APK.
Production responsibilities¶
- authenticate the WebSocket upgrade;
- validate messages on the server;
- persist delivery state and message history;
- define ordering, deduplication and retry semantics;
- use push notifications when the process is not connected.
ApkPy provides WSS framing, ping/pong handling, bounded pending sends and backoff reconnection. It does not provide end-to-end encryption or a chat backend.
For rows that read as a conversation, with bubbles on each side, see Components and layouts; for messages that arrive while the app is closed, see Push with Firebase.