Table Of Contents
Migrating from Kivy 2.x.x to Kivy 3.x.x¶
Introduction¶
Kivy 3.x.x introduces several changes and improvements compared to Kivy 2.x.x. This guide will help you migrate your existing Kivy 2.x.x codebase to Kivy 3.x.x.
Renamed modules and environment variables¶
Migration from kivy.core.audio to kivy.core.audio_output
In Kivy 3.x.x, the kivy.core.audio module has been renamed as kivy.core.audio_output.
Import Statement Changes
To migrate your code, you need to update the import statements in your codebase. For example, if you have the following import statement in your code:
from kivy.core.audio import SoundLoader
You need to update it to:
from kivy.core.audio_output import SoundLoader
Environment Variable Changes
The environment variable has also been renamed from KIVY_AUDIO to KIVY_AUDIO_OUTPUT.
If you were using the KIVY_AUDIO environment variable to specify audio provider preferences, you need to update it to KIVY_AUDIO_OUTPUT. For example:
in Python before importing Kivy:
import os
# Kivy 2.x.x
os.environ['KIVY_AUDIO'] = 'sdl3,gstplayer'
import kivy
# Kivy 3.x.x
os.environ['KIVY_AUDIO_OUTPUT'] = 'sdl3,gstplayer'
import kivy
Removals¶
Removal of `.play` property from `kivy.uix.video.Video` and `kivy.uix.videoplayer.VideoPlayer`
In Kivy 3.x.x, the .play property has been removed from the kivy.uix.video.Video and kivy.uix.videoplayer.VideoPlayer classes.
To migrate your code, you need to update the references to the .play property in your codebase. For example, if you have the following code in your Kivy 2.x.x codebase:
video = Video(source='video.mp4')
# Play the video
video.play = True
# Stop the video
video.play = False
You need to update it to:
video = Video(source='video.mp4')
# Play the video
video.state = 'play'
# Stop the video
video.state = 'stop'
# Pause the video
video.state = 'pause'
Removal of `padding_x` and `padding_y` Properties from `kivy.uix.textinput.TextInput`
In Kivy 3.x.x the padding_x and padding_y properties have been removed from the kivy.uix.textinput.TextInput class. Instead, padding is now managed through the unified padding property.
To update your code, replace instances of padding_x and padding_y with the padding property.
The padding property accepts a list of values, allowing for more flexible padding configurations:
[horizontal, vertical] — e.g., [10, 10]
[padding_left, padding_top, padding_right, padding_bottom] — e.g., [10, 5, 10, 5]
For more details on how to use the padding property, please refer to the related documentation.
Removal of `file_encodings` Property from `kivy.uix.filechooser.FileChooserController`
In Kivy 3.x.x, the file_encodings property has been removed from the kivy.uix.filechooser.FileChooserController class.
The file_encodings property was deprecated and it was kept for backward compatibility, however it was just ignored and not used internally.
To migrate your code, you just need to remove any references to the file_encodings property in your codebase.
Removal of deprecated `on_dropfile` Window event name
In Kivy 3.x.x, the previously deprecated on_dropfile event name has been removed. Use on_drop_file instead.
The event was renamed in Kivy 2.1.0, so any remaining compatibility code that still binds to on_dropfile now needs to be updated.
# Kivy 2.x.x (legacy/deprecated name)
from kivy.core.window import Window
def handle_drop(window, filename):
print(filename)
Window.bind(on_dropfile=handle_drop)
# Kivy 3.x.x
from kivy.core.window import Window
def handle_drop(window, filename, x, y, *args):
print(filename, x, y)
Window.bind(on_drop_file=handle_drop)
If you dispatch or override the event directly, also rename any on_dropfile method implementations to on_drop_file.
Removal of the Kv-lang Templates feature
In Kivy 3.x.x, the deprecated Kivy language Templates feature (introduced in 1.0.5, deprecated in 1.7.0) has been removed. The following are gone:
The
[Name@Base]:Kv-lang template syntax (any kv file that contains a[...]:selector will now raise aParserExceptionat load time).Builder.template(name, **ctx)and theBuilder.templatesdict.Factory.is_template()and theis_template=keyword argument ofFactory.register().
Migrate to dynamic classes (<Name@Base>:). Dynamic classes have largely
superseded templates since Kivy 1.7.0 and support normal Kivy properties,
binding and inheritance.
Migrating a template in `.kv`
# Kivy 2.x.x
[IconItem@BoxLayout]:
Image:
source: ctx.image
Label:
text: ctx.title
# Kivy 3.x.x
<IconItem@BoxLayout>:
image: ''
title: ''
Image:
source: root.image
Label:
text: root.title
Note the two changes: [...]: becomes <...>:, and ctx.foo references
become root.foo references against properties declared on the rule itself.
Migrating `Builder.template(…)` instantiation
# Kivy 2.x.x
from kivy.lang import Builder
icon = Builder.template('IconItem', title='Hello', image='myimage.png')
# Kivy 3.x.x
from kivy.factory import Factory
icon = Factory.IconItem()
icon.title = 'Hello'
icon.image = 'myimage.png'
Because dynamic-class properties are added to the widget by the rule (rather
than declared on the class), they are not yet present when __init__
processes its kwargs - so pass values via setattr (or property assignment)
after construction rather than as constructor kwargs. If you need
constructor-kwarg support, define the widget as a regular Python class with
explicit Property declarations instead.
AccordionItem: `title_template` and `title_args` replaced by `title_class`
The AccordionItem widget previously used the Kv-lang
templates feature to render its title bar via the title_template (string)
and title_args (dict) properties. Both properties have been removed.
The replacement is title_class. It accepts
either a class object or a Factory-resolvable string. The class is instantiated
with two keyword arguments: title and item.
To customise the appearance of the title widget, subclass
AccordionItemTitle (or write any widget that
accepts title and item kwargs) and pass it via title_class.
See also
title_class documentation.
Clock¶
Improved @triggered Decorator Behavior, Instance Isolation and Debouncing
In Kivy 3.x.x, the triggered() decorator has been significantly
improved. Previously, when used as a method decorator, it shared a single trigger
and state across all instances of a class. This meant that calling the method on
one instance would throttle calls on all other instances, and arguments from
different instances could overwrite each other.
Behavior Changes
Improved Instance Isolation: Each instance now has its own isolated trigger and argument storage. Calling a triggered method on
widget_ano longer affectswidget_b.Improved Lazy Initialization: Triggers are now created only when the decorated function is first called, improving initialization performance.
New is_triggered Property: A new
is_triggeredproperty was added to the decorated function/method, allowing you to check if a call is currently pending.New debounce Parameter: A new
debounce=False(default) parameter was added.Throttling (default): Subsequent calls while a trigger is active update the arguments but do not reset the timer. The function fires once after the initial timeout.
Debouncing (
debounce=True): Subsequent calls cancel any pending execution and reschedule it. The function only fires after the caller stops calling it for the duration of thetimeout.
Migration Impact
This is primarily a bug fix and a set of new features. It should not
require code changes for most applications. However, if your codebase
intentionally relied on the legacy shared throttling behavior across different
instances, you can restore this behavior by using the ``@classmethod``
decorator above @triggered.
This ensures the trigger is bound to the class object rather than individual instances, restoring the shared behavior in an idiomatic way.
class MyWidget(Widget):
# Default in 3.x.x: Isolated per instance
@triggered(0.1)
def sync_ui(self, *args):
pass
# Shared behavior (same as legacy 2.x.x): Shared by all instances
@classmethod
@triggered(0.1)
def sync_shared_data(cls, *args):
pass
# Optional: Debouncing (0.1s from the LAST call)
@triggered(0.1, debounce=True)
def search_input(self, text):
pass
AliasProperty¶
Consistent Dispatch for the First Change and for None Getter Results
In Kivy 3.x.x, AliasProperty follows these rules when
one of its bind properties changes (see
#6901 and
#9405):
With
cache=True, it dispatches when the getter returns a new value. The first change to abindproperty after the object is created always dispatches, even if the getter returns the same value.With
cache=False, it dispatches on every change to abindproperty.
Behavior Changes
A read no longer suppresses the first dispatch (
cache=True). In Kivy 2.x.x, reading the property before its firstbindchange primed the cache, and that change was then dispatched only if the getter’s value differed. The first change now dispatches whether or not the property was read.Fixed missed dispatches when the getter returns
None(cache=False). In Kivy 2.x.x, abindchange that made the getter returnNonewas not dispatched.
from kivy.event import EventDispatcher
from kivy.properties import NumericProperty, AliasProperty
class Rect(EventDispatcher):
width = NumericProperty(0)
height = NumericProperty(0)
def _get_aspect_ratio(self):
return (self.width / self.height) if self.height else None
aspect_ratio = AliasProperty(
_get_aspect_ratio, None, bind=('width', 'height'), cache=False)
def on_aspect_ratio(self, instance, value):
print(f'aspect_ratio: {value}')
# Kivy 2.x.x
r = Rect(width=100) # no dispatch
r.height = 1 # prints "aspect_ratio: 100.0"
r.height = 0 # missing dispatch - `on_aspect_ratio` is NOT called
# Kivy 3.x.x
r = Rect(width=100) # prints "aspect_ratio: None"
r.height = 1 # prints "aspect_ratio: 100.0"
r.height = 0 # prints "aspect_ratio: None"
Migration Impact
These are bug fixes, and most applications need no changes. A callback on a
cached AliasProperty that reads the property before its first bind
change may now be called once for that change even though the value did not
change. A callback on a non-cached AliasProperty is now also called when
the getter returns None. If the callback should only run when the value
changes, compare against the previous value inside the callback.
Mouse Input Provider¶
Multiple Mouse Buttons Can Now Be Held Down Simultaneously
In Kivy 3.x.x, the mouse input provider
(MouseMotionEventProvider) has been
fixed to correctly track multiple mouse buttons that are held down at the
same time when multitouch simulation is disabled (disable_multitouch)
(see #3597).
Behavior Changes
Multiple simultaneous button-drags are now tracked independently. Previously, the provider only tracked a single “current drag” at a time. If a second mouse button was pressed while a first button was still held down, the second button’s press was silently ignored (no
on_touch_downwas dispatched for it) until the first button was released. Now, each button gets its own independent touch whendisable_multitouchis set, so pressing a second (or third) button while another is held correctly dispatches its ownon_touch_down/on_touch_move/on_touch_upsequence.This change only affects behavior when
disable_multitouchis active. When multitouch simulation is enabled (the default), only one simulated touch (the red dot) is tracked at a time, as before.
from kivy.app import App
from kivy.uix.widget import Widget
class MainWindow(Widget):
def on_touch_down(self, touch):
print(f'{touch.button} is being pressed')
class TestingApp(App):
def build(self):
return MainWindow()
if __name__ == '__main__':
TestingApp().run()
# config: [input] mouse = mouse,disable_multitouch
# Kivy 2.x.x
# press and hold left -> "left is being pressed"
# press right while left is still held -> (nothing printed)
# Kivy 3.x.x
# press and hold left -> "left is being pressed"
# press right while left is still held -> "right is being pressed"
Migration Impact
This is primarily a bug fix. It should not require code changes for
most applications, and mainly benefits applications using
disable_multitouch that need to respond to more than one mouse button
at a time (e.g. left + right held together).
SVG¶
Removal of the experimental kivy.graphics.svg module
In Kivy 3.x.x, the experimental Svg canvas instruction in
kivy.graphics.svg has been removed, along with its example scripts under
examples/svg/ (benchmark.py, main.py, main-smaa.py) and its
Factory registration. The module had been marked experimental since its
introduction and is superseded by the new SVG support added in Kivy 3.x.x.
Replacement: SvgWidget / AsyncSvgWidget
For most use cases, drop in SvgWidget (local sources)
or AsyncSvgWidget (network sources):
# Kivy 2.x.x
from kivy.graphics.svg import Svg
with widget.canvas:
Svg('image.svg')
# Kivy 3.x.x
from kivy.uix.svg import SvgWidget
widget.add_widget(SvgWidget(source='image.svg'))
In KV language:
# Kivy 3.x.x
SvgWidget:
source: 'image.svg'
Replacement: kivy.core.svg image provider
For loading SVGs through the standard image pipeline (for example as a
texture for Image), the new kivy.core.svg
provider is selected automatically when an .svg source is loaded; no
explicit import or Factory registration is required.
Factory registration
The Svg Factory entry that pointed at kivy.graphics.svg has been
removed. SvgWidget and AsyncSvgWidget are registered in the Factory
under their own names and can be used directly from KV.
Application Storage Directories¶
Linux user_data_dir Path Change (XDG Compliance Fix)
In Kivy 3.x.x, the App.user_data_dir path on Linux has been corrected to follow
the XDG Base Directory specification. Previously, it incorrectly used XDG_CONFIG_HOME
(for configuration files); it now correctly uses XDG_DATA_HOME (for application data).
Path Changes on Linux:
Property |
Kivy 2.x.x |
Kivy 3.x.x |
|---|---|---|
|
|
|
Impact:
If your Linux application uses App.user_data_dir to store user data, the data
will now be stored in a different location after upgrading to Kivy 3.x.x. This is
the correct XDG-compliant location, but existing apps may need to migrate their data.
Migration Options:
Manual Migration (Recommended for production apps)
Move existing data from the old location to the new location during app startup:
Note: Windows, macOS, iOS, and Android paths are unchanged.
New App.user_cache_dir Property
Kivy 3.x.x introduces a new App.user_cache_dir property for temporary/cache data
that the system may delete at any time.
This is not a breaking change - it’s a new optional property. Existing apps continue to work unchanged.
Platform Paths:
Windows:
%APPDATA%\<app_name>\CachemacOS:
~/Library/Caches/<app_name>Linux:
~/.cache/<app_name>(respects$XDG_CACHE_HOME)Android:
Context.getCacheDir()iOS:
~/Library/Caches/<app_name>
New KIVY_DESKTOP_PATH_ID Environment Variable
Kivy 3.x.x introduces KIVY_DESKTOP_PATH_ID to set user-friendly application
directory names on desktop platforms.
This is not a breaking change - it’s opt-in. Existing apps continue to work unchanged unless you explicitly set this environment variable.
Key Feature:
Setting KIVY_DESKTOP_PATH_ID creates an application-specific location for
the .kivy directory containing the config and log files. Without
KIVY_DESKTOP_PATH_ID, the config and logs are placed in a single global
.kivy directory (~/.kivy).
This means multiple Kivy applications can now have their own isolated configuration and log directories, preventing conflicts between different applications.
When Set:
The variable provides a human-readable application title for directories, making it easier for end users to identify your app’s directories when browsing their filesystem.
Example:
import os
os.environ['KIVY_DESKTOP_PATH_ID'] = 'My Photo Editor'
from kivy.app import App
# On Windows, creates: %APPDATA%\My_Photo_Editor\.kivy
# Instead of: %APPDATA%\photoeditor\.kivy
Priority:
KIVY_DESKTOP_PATH_ID takes highest priority and affects:
KIVY_HOMEdirectory (overridesKIVY_HOMEenv var and venv detection)App.user_data_dirdirectoryApp.user_cache_dirdirectory
Platform Behavior:
Desktop platforms (Windows, macOS, Linux): Uses normalized path_id for directory names
Mobile platforms (iOS, Android): Ignored - uses
App.nameas before
Desktop Path Examples with KIVY_DESKTOP_PATH_ID=’My Photo Editor’:
Directory |
Path |
|---|---|
KIVY_HOME |
|
user_data_dir |
|
user_cache_dir |
|
Warning:
If you set KIVY_DESKTOP_PATH_ID in an existing app, your data will move to a new
location. You may need to migrate existing data (see Linux migration example above).
For complete documentation, see Controlling the environment and examples/desktop_path_id/.
```