Skip to content

ApkPy 1.14.1

Released 2026-10-25. python -m pip install --upgrade apkpy==1.14.1.

Classes reach the phone. A class in writehere.py or in a helper module is translated to Java, and an object behaves as it does in CPython. @dataclass and errors of your own come with it.

from dataclasses import dataclass, field


class SoldOut(Exception):
    pass


@dataclass
class Cart:
    owner: str
    items: list = field(default_factory=list)

    def add(self, name, stock):
        if stock == 0:
            raise SoldOut(f"{name} is sold out")
        self.items.append(name)


def buy():
    cart = Cart("ana")
    try:
        cart.add("tea", 3)
        cart.add("cake", 0)
    except SoldOut as e:
        out.set_value(f"{e}: {cart}")

On the phone the label reads cake is sold out: Cart(owner='ana', items=['tea']), the same as in CPython.

Classes

  • __init__, methods, one parent class and super(), class attributes (Shape.count += 1), @staticmethod, @property, and __str__, __repr__ and __eq__.
  • Every name that holds an object sees the same changes, so tasks[0].done = True is seen through the list and through any other name for that task. is asks whether two names hold the same object, isinstance() and type(x).__name__ follow the family tree, and the method that runs is the one of the object's own class. Reading a field that was never set raises AttributeError.
  • A field that is always given the same kind is read as that kind, so self.price * self.qty is arithmetic and self.items.append(x) changes the object's own list.
  • A method can be handed over as a value, command=cart.clear. An object made at the top of the file is made once, when the app starts, and every screen sees the same one.
  • A helper module can hold classes: import shop, then shop.Product("tea", 2.0).

Dataclasses

@dataclass gives a class the __init__, __repr__ and __eq__ that CPython writes from its fields, unless the class writes its own. The fields take field(default=...), field(default_factory=list), init=, repr= and compare=; a dataclass can extend another, and ClassVar and __post_init__ work as in CPython.

Errors of your own

A class that extends an error is raised and caught like Python's own: except ValueError catches class TooLong(ValueError): pass, except AppError catches the errors that extend it, and str(e) is the message. An __init__ that hands the message on with super().__init__(message) can give it a default, used by raise NotFound().

A few forms stop the build at their line, each with one that compiles: frozen=True and order=True on a dataclass, @classmethod, and fields or methods on an error class. The whole list is on Compatibility and limits.

Checked on a phone

Two new examples were built and run on a phone with Android 16: examples/44_classes.py, a month's budget, and examples/45_dataclasses_and_errors.py, a pantry list. At every step each showed the labels that the same app shows in CPython, errors included. The tests also run the generated Java of whole apps on the JVM and compare it with CPython, line by line.

Fixed

  • A module-level list that a function gives a new value with global (ITEMS = [...] inside a function) was walked by for and searched by in as the list written at the top of the file, before the function ran. ITEMS[0] already read the new one. Present in 1.14.0.
  • str() of a comparison or of a flag, put in a list ([str(a == b), "x"]), showed as true / false when the list was shown, where CPython shows True / False.
  • The AGENTS.md that apkpy init writes listed as refused much of the Python that 1.14.0 translates. It lists what is translated now, classes included.

Upgrade

python -m pip install --upgrade apkpy==1.14.1, and build again. Nothing in an app has to change. Code with classes that stopped the build in 1.14.0 may now build.

What stays different

An object shows its address, <__main__.Task object at 0x...>, and the address differs from CPython's, as it does between two runs of CPython. A list written as a default value, def __init__(self, tags=[]), is a new list for each call on the phone, where CPython shares one list between the calls; field(default_factory=list) gives every object its own in both.