Skip to content

The connection

Whether the phone is online, over what, and being told when that changes: the "No connection" banner, sending what waited once the connection is back, keeping a big download for Wi-Fi.

from apkpy_lib import *

home = Screen(id="home")
online = state(True)

banner = label("No connection", id="banner", screen=home)
online.bind_visibility(banner, when=False)


def changed():
    online.set(network.online())


def opened():
    changed()
    network.on_change(changed)


lifecycle(home, on_mount=opened)
run(start_screen=home)
Call
network.online() Whether the phone reaches the internet right now — for an if.
network.kind() What the connection is: "wifi", "mobile", "ethernet", "other" or "none".
network.metered() Whether the person pays for what goes over it — for an if.
network.on_change(function) Call the function whenever one of those three answers changes.

Nobody is asked for anything: the permission this needs is one Android gives every app, and ApkPy adds it to the manifest when you use one of these calls.

Online

network.online() is yes when the phone has a connection and has seen it reach the internet. That second half matters:

The phone is on… kind() online()
Wi-Fi at home wifi yes
Mobile data mobile yes
A hotel Wi-Fi that wants a login page first wifi no
A router whose line is down wifi no
Aeroplane mode, or no signal none no
Wi-Fi, through a VPN wifi yes
Aeroplane mode, with a VPN app still running none no

The last row is one Android gets wrong on its own: it hands every app the VPN's tunnel, and the tunnel stays up, still marked as reaching the internet, with nothing beneath it. ApkPy looks beneath.

It is the answer to "is it worth trying?". It is not a promise that the next request works — your own server can be down with the phone perfectly online — so https still tells you how each call went.

def send():
    if network.online():
        https.post(URL, data={"text": text.get_value()}, on_response=sent)
    else:
        note.set_value("Waiting for a connection")

Metered

Mobile data, and a Wi-Fi that is somebody's phone shared as a hotspot, are connections the person pays for by what goes over them. network.metered() says so, and is the question to ask before a large download — rather than kind() == "wifi", which is wrong for a hotspot and for a mobile plan with no limit.

def download():
    if network.online() and not network.metered():
        downloads.start(MAP_URL, on_result=landed)
    else:
        note.set_value("Waiting for Wi-Fi")

Being told

network.on_change(changed) calls changed() whenever what online(), kind() or metered() answer is no longer what it was.

The function is one you wrote with def at the top level of the file, that takes nothing — it is told that something changed, and asks what:

def changed():
    online.set(network.online())
    kind.set_value(network.kind())

Write its name without brackets. A lambda, changed() with its brackets, or a function that takes arguments stops the build and says which line to write instead (U2048); the Previewer refuses them in the same words.

It is told of changes, not of how things are. Asking does not call the function, so call it once yourself as the screen opens, as opened() does at the top of this page.

It belongs to the screen that asked, and is told only while that screen is on show:

  • a change that comes and goes while the screen is away — the app in the background, another screen on top — is not told;
  • one that is still there when the screen comes back is told once, then;
  • closing the screen ends it. Turning the phone or changing the theme does not: Android builds the screen again and it keeps what it asked for.

Asking twice with the same function does nothing more; a screen can ask with several functions, and each is called once for a change.

Wi-Fi to mobile data is one change. Walking out of the house, the phone has mobile data before it lets go of the Wi-Fi: the function is called once, with kind() now "mobile" — not for a loss and then a return.

Ask from a function — the screen's on_mount, or a button. A network.on_change(...) at the top of the file would run before there is a screen for it to belong to, and stops the build (U2048).

Bind it to a state, as at the top of this page:

online = state(True)
online.bind_visibility(banner, when=False)      # shown while online is False

and set the state where the connection is read: online.set(network.online()). The banner then starts hidden, on the phone and in the Previewer, with nothing to flash as the app opens.

What a change is not for

Work that has to wait for a connection with the app closed: that is background_job(..., requires_network=True), which Android runs when the connection is there. Inside a background job these calls stop the build (J7004): the job is already told to wait for the network.

In the Previewer

A computer is never in a lift, so nothing an app does without a connection would ever be seen at the desk. The connection is the Previewer's to take away: click the connection in the status bar, top right, and it goes from Wi-Fi to mobile data to none, and round again.

The status bar shows kind() online() metered()
Bars and the Wi-Fi fan wifi yes no
Bars alone mobile yes yes
An aeroplane none no no

With none, an https call fails with the words Android gives on a phone that is offline (Unable to resolve host …), and a background_job that needs the network waits. Pictures, uploads and a websocket still use the computer's own connection.

"ethernet", "other" and a Wi-Fi that is not online are answers a phone gives and the Previewer does not stand in for.