Slot System

更新时间:
复制 MD 格式

The Slot System is a core architectural design of AliPlayerKit. It decomposes the player UI into independent, pluggable components through componentization. A unified system manages these components to achieve UI decoupling, flexible composition, and extensible customization.

Introduction to Concepts

What is the Slot System?

The Slot System is one of AliPlayerKit’s core architectural designs. It is a plug-in management mechanism for UI components.

It enables developers to dynamically add, remove, or replace UI components at any position in the player interface, resulting in a highly customizable player UI.

What is a Slot?

A Slot is a reserved view container that holds a group of related UI components.

Each slot has its own lifecycle, display and hide logic, and event management. It can automatically show or hide internal components based on conditions such as playback status or scenario type.

Features

Problems Solved

  • Tight coupling between UI components and player logic.

  • Customizing the interface requires modifying framework source code.

  • Limited flexibility when combining components across different scenarios.

  • Complex component lifecycle management.

Core Value

The Slot System independently decomposes player UI components. Developers can choose which ones to use and how to configure them.

Usage

Description

Advantages

Use default interface

The player component uses the official default UI

Simplifies integration, reduces integration costs, and enables low-code integration.

Custom use

Replace some or all slot implementations

Meets various rich UI requirements.

Do not use slots

Use only playback capabilities

Pure playback scenarios with no UI dependencies.

Architectural Advantages:

  • Decoupling: UI components are separate from core player logic, with clearly defined responsibilities.

  • Flexibility: Dynamically combine and replace UI components at runtime.

  • Extensibility: Customize the UI without modifying the framework, enabling strong extensibility.

  • Separation of concerns: UI styles (XML layouts) are separate from business logic (Slot class handling).

Core Capabilities

Capability

Description

Dynamic combination

Freely combine UI components at runtime.

Hot swapping

Replace component implementations without restarting.

Scenario adaptation

Automatically switch UI behavior for different playback scenarios.

Built-in Components

AliPlayerKit provides a set of out-of-the-box built-in components that cover common player UI requirements.

Slot Types

Slot Type

Description

Default Implementation

PLAYER_SURFACE

Video display, supports various rendering views

DisplayViewSlot / SurfaceViewSlot / TextureViewSlot

FULLSCREEN

Full screen management, handles screen orientation switching

FullscreenSlot

GESTURE_CONTROL

Gesture control, handles click/double-click/long press/drag

GestureControlSlot

LANDSCAPE_HINT

Landscape viewing hint, guides users to full screen

LandscapeHintSlot

COVER

Cover image, displays video cover before playback

CoverSlot

CENTER_DISPLAY

Center display, shows status such as playback speed/brightness/volume

CenterDisplaySlot

PLAY_STATE

Playback status, shows loading/error prompts, etc.

PlayStateSlot

LOG_PANEL

Log panel, displays player logs during debugging

LogPanelSlot

TOP_BAR

Top control bar, shows back/title/settings, etc.

TopBarSlot

BOTTOM_BAR

Bottom control bar, shows playback controls/progress bar, etc.

BottomBarSlot

SETTING_MENU

Settings menu, shows settings such as playback speed/definition/mirroring

SettingMenuSlot

Scenario Adaptation

AliPlayerKit defines five playback scenarios. Slot behavior automatically adapts to each scenario:

Scenario

Description

Typical Applications

VOD

Video-on-demand scenario, supports all features.

Regular video playback.

LIVE

Live streaming scenario, timeline operations disabled.

Real-time live streams.

VIDEO_LIST

List playback scenario, vertical gestures disabled.

Information feeds, short video lists.

RESTRICTED

Restricted playback scenario, limits seeking.

Education and training, exam monitoring.

MINIMAL

Minimal playback scenario, only displays the video screen.

Background videos, decorative videos.

Slot Visibility Rules:

Slot

VOD

LIVE

VIDEO_LIST

RESTRICTED

MINIMAL

PLAYER_SURFACE

FULLSCREEN

GESTURE_CONTROL

LANDSCAPE_HINT

COVER

CENTER_DISPLAY

PLAY_STATE

LOG_PANEL

Configurable

Configurable

Configurable

Configurable

TOP_BAR

BOTTOM_BAR

SETTING_MENU

Gesture Behavior Differences:

Gesture

VOD

LIVE

VIDEO_LIST

RESTRICTED

MINIMAL

Click

Show/hide control bar

Show/hide control bar

Show/hide control bar

Show/hide control bar

Disabled

Double-click

Toggle play/pause

Toggle play/pause

Toggle play/pause

Toggle play/pause

Disabled

Long press

2× speed playback

Disabled

2× speed playback

Disabled

Disabled

Horizontal drag

Progress Navigation

Disabled

Progress navigation

Disabled

Disabled

Left vertical drag

Adjust brightness

Adjust brightness

Disabled

Adjust brightness

Disabled

Right vertical drag

Adjust volume

Adjust volume

Disabled

Adjust volume

Disabled

Bottom Control Bar Differences:

Control

VOD

LIVE

VIDEO_LIST

RESTRICTED

MINIMAL

Play/pause button

Progress bar

Draggable

Not draggable

Draggable

Not draggable

Time display

Refresh button

Full screen button

Basic Usage

The Slot System offers three usage strategies. Developers can select the appropriate method based on their needs:

Strategy

Description

Applicable Scenarios

Use default interface

The simplest method; the player component uses the default interface.

Quick integration, standard playback scenarios.

Customize some slots

Customize only specific slots; others use the default interface.

Partial customization, retaining default interactions.

Fully customize interface

Customize all slots to create a fully personalized player interface.

Deep customization, brand-specific UI.

Strategy One: Use Default Configurations

The simplest approach. Directly use the built-in slot configurations:

// 1. Get the player view
AliPlayerView playerView = findViewById(R.id.player_view);

// 2. Create the controller and playback data
AliPlayerController controller = new AliPlayerController(this);
AliPlayerModel model = new AliPlayerModel.Builder()
        .videoSource(videoSource)
        .build();

// 3. Bind to the view (automatically uses default slots)
playerView.attach(controller, model);

Strategy Two: Customize Some Slots

Customize only specific slots; others retain the default interface. For example, customize only the top bar:

// 1. Create a registry
SlotRegistry registry = new SlotRegistry();

// 2. Register only the slots to customize
registry.register(SlotType.TOP_BAR, parent -> new MyTopBarSlot(parent.getContext()));

// 3. Pass the registry during binding (unregistered slots use default implementation)
playerView.attach(controller, model, registry);

Strategy Three: Fully Customize Interface

Customize all slots to create a fully personalized player interface:

// 1. Create a registry
SlotRegistry registry = new SlotRegistry();

// 2. Register all slots
registry.register(SlotType.PLAYER_SURFACE, parent -> new MySurfaceSlot(parent.getContext()));
registry.register(SlotType.COVER, parent -> new MyCoverSlot(parent.getContext()));
registry.register(SlotType.TOP_BAR, parent -> new MyTopBarSlot(parent.getContext()));
registry.register(SlotType.BOTTOM_BAR, parent -> new MyBottomBarSlot(parent.getContext()));
// ... Register other slots

// 3. Pass the registry during binding
playerView.attach(controller, model, registry);     

Advanced Usage

How to Implement Dynamic Slot Switching?

Dynamically switch slot implementations at runtime:

// Get the manager
ISlotManager slotManager = playerView.getSlotManager();

// Switch Surface type
registry.register(SlotType.PLAYER_SURFACE,
    parent -> new TextureViewSlot(parent.getContext()));
slotManager.rebuildSlots();        

How to Implement Custom Slots?

AliPlayerKit provides two ways to implement custom slots. Developers can select the appropriate method based on their requirements.

Method One: Inherit BaseSlot (Recommended)

Inheriting BaseSlot is the simplest method. The framework encapsulates common logic such as lifecycle management and event subscription.

Applicable Scenarios: Most UI slots, such as covers, control bars, and status displays.

  1. Create the layout file.

    Create the layout file in the res/layout/ directory:

    <!-- res/layout/my_cover_layout.xml -->
    <?xml version="1.0" encoding="utf-8"?>
    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <ImageView
            android:id="@+id/cover_image"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:scaleType="centerCrop" />
    
    </FrameLayout>
  2. Create the Slot class.

    Inherit BaseSlot and override the necessary methods:

    public class MyCoverSlot extends BaseSlot {
    
        public MyCoverSlot(@NonNull Context context) {
            super(context);
        }
    
        @Override
        protected int getLayoutId() {
            return R.layout.my_cover_layout;  // Return the layout ID
        }
    
        @Override
        public void onBindData(@NonNull AliPlayerModel model) {
            // Bind data
            ImageView coverImage = findViewByIdCompat(R.id.cover_image);
            Glide.with(getContext()).load(model.getCoverUrl()).into(coverImage);
        }
    
        @Override
        public void onUnbindData() {
            // Clean up resources
            ImageView coverImage = findViewByIdCompat(R.id.cover_image);
            Glide.with(getContext()).clear(coverImage);
        }
    }
  3. Register and use.

    SlotRegistry registry = new SlotRegistry();
    registry.register(SlotType.COVER, parent -> new MyCoverSlot(parent.getContext()));
    playerView.attach(controller, model, registry);

Method Two: Implement the ISlot Interface

Directly implementing the ISlot interface provides greater flexibility. However, you must manage the lifecycle manually.

Applicable Scenarios: Requires full control over the view hierarchy or needs to inherit specific View types (such as SurfaceView or TextureView).

  1. Create the layout file.

    Create the layout file in the res/layout/ directory:

    <!-- res/layout/my_cover_layout.xml -->
    <?xml version="1.0" encoding="utf-8"?>
    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <ImageView
            android:id="@+id/cover_image"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:scaleType="centerCrop" />
    
    </FrameLayout>
  2. Create the Slot class.

    Implement the ISlot interface and gain slot capabilities by composing SlotBehavior:

    public class MySurfaceSlot extends FrameLayout implements ISlot, ISurfaceProvider {
    
        // Gain core slot capabilities through composition
        private final SlotBehavior slotBehavior = new SlotBehavior();
        private SlotHost host;
    
        public MySurfaceSlot(@NonNull Context context) {
            super(context);
            View.inflate(context, R.layout.my_surface_layout, this);
        }
    
        @Override
        public void onAttach(@NonNull SlotHost host) {
            // 1. Delegate lifecycle handling to SlotBehavior
            slotBehavior.attach(host);
            // 2. Save host reference
            this.host = host;
            // 3. Set up Surface
            setupSurfaceProvider(host);
        }
    
        @Override
        public void onDetach() {
            if (host != null) {
                onSurfaceDestroyed(host);
            }
            slotBehavior.detach();
            host = null;
        }
    
        @Override
        public void onBindData(@NonNull AliPlayerModel model) {
            // Data binding logic
        }
    
        @Override
        public void onUnbindData() {
            // Data cleanup logic
        }
    
        @Override
        public void setupSurfaceProvider(@Nullable SlotHost host) {
            // Surface setup logic
        }
    }
  3. Register and use.

    SlotRegistry registry = new SlotRegistry();
    registry.register(SlotType.COVER, parent -> new MyCoverSlot(parent.getContext()));
    playerView.attach(controller, model, registry);

Method Comparison

Feature

Inherit BaseSlot

Implement ISlot Interface

Code volume

Less, focus only on business logic

More, manual lifecycle handling required

Flexibility

Medium, inherits FrameLayout

High, can inherit any View type

Lifecycle

Framework automatically manages

Must manually delegate to SlotBehavior

Recommended scenarios

Most UI slots

Requires special view types

How to Restore the UI?

When restoring the player UI in a project, handle different scenarios as follows:

Scenario One: Official Slots Not Provided

If the official player component does not provide the slot type you need, implement it yourself:

  1. Create custom slots.

    Place custom slots in the ui/slots/custom directory to distinguish them from official implementations:

    your-module/src/main/java/com/yourpackage/
    └── ui/
        └── slots/
            └── custom/
                ├── MyDanmakuSlot.java      // Live comment slot
                ├── MySubtitleSlot.java     // Caption slot
                └── MyWatermarkSlot.java    // Watermark slot

Scenario Two: Official Slots Provided but Not Needed

If the official player component provides slots but your scenario does not require them, handle this in one of two ways:

  • Method One: Disable via configuration (recommended).

    Remove the corresponding configuration item in the createDefaultConfigs() method of SlotConstants:

    // Before modification: SlotRegistry injects default components
    configs.add(new SlotConfig.Builder()
            .type(SlotType.LOG_PANEL)
            .excludeScenes(createSet(SceneType.MINIMAL))
            .condition(AliPlayerKit::isLogPanelEnabled)
            .build());
    
    // After modification: Comment out or delete this configuration; SlotRegistry will no longer inject default components
    // configs.add(new SlotConfig.Builder()
    //         .type(SlotType.LOG_PANEL)
    //         ...
    //         .build());
  • Method Two: Physical deletion (not recommended).

    Directly delete the corresponding Slot class file and its related resources. This is not recommended.

Scenario Three: Official Slot UI Style Does Not Meet Requirements

If the official slot’s UI style does not meet your requirements, modify the XML layout file directly to restore the UI. Thanks to the separation of style and logic, layout modifications do not affect business logic.

  1. Locate the corresponding layout file.

    Layout files are in the playerkit/src/main/res/layout/ directory, with a naming convention of layout_{slot_type}.xml:

    Slot Type

    Layout File

    TOP_BAR

    layout_top_bar_slot.xml

    BOTTOM_BAR

    layout_bottom_bar_slot.xml

    COVER

    layout_cover_slot.xml

    PLAY_STATE

    layout_play_state_slot.xml

  2. Modify the layout file.

    Directly modify the style attributes in the XML file. Ensure control IDs remain unchanged:

    <!-- Before modification: Official default style -->
    <LinearLayout
        android:background="#80000000"
        android:padding="8dp">
    
        <ImageView
            android:id="@+id/iv_back"
            android:layout_width="40dp"
            android:layout_height="40dp" />
    </LinearLayout>
    
    <!-- After modification: Custom style (keep ID unchanged) -->
    <LinearLayout
        android:background="@drawable/custom_top_bar_bg"
        android:padding="12dp">
    
        <ImageView
            android:id="@+id/iv_back"
            android:layout_width="48dp"
            android:layout_height="48dp"
            android:src="@drawable/ic_custom_back" />
    </LinearLayout>
  3. Architectural constraints.

    The following constraints ensure that custom code does not conflict with official code:

    1. Control IDs must be fixed. The Slot class finds controls and binds events using their IDs.

    2. Layout structure is adjustable, but core controls must be retained.

    3. Business logic does not require modification; it automatically adapts to the new layout.

  4. Upgrade compatibility recommendations.

    To make extensive UI modifications, we recommend using the Custom Slot strategy to avoid conflicts during future AliPlayerKit upgrades:

    • Do not directly modify official layout files or Slot classes.

    • Create V2 versions (such as TopBarSlotV2.java and layout_top_bar_slot_v2.xml).

    • Register through SlotRegistry or modify SlotConstants to replace the default implementation.

    public class TopBarSlotV2 extends BaseSlot {
    
        @Override
        protected int getLayoutId() {
            return R.layout.layout_top_bar_slot_v2;
        }
    }

How to Implement Headless Slots?

The Slot System supports both UI componentization and business logic componentization. In addition to UI slots, you can extend functionality through headless slots, which implement business logic without rendering any UI.

Example: Fullscreen Management Slot

The official FullscreenSlot is a typical headless slot. It manages fullscreen switching logic only and has no UI rendering capability.

public class FullscreenSlot extends BaseSlot {

    @Override
    protected int getLayoutId() {
        return 0;  // Return 0 for no layout, no UI rendering
    }

    @Override
    public void onAttach(@NonNull SlotHost host) {
        super.onAttach(host);
        // Listen for fullscreen toggle events
    }

    @Override
    protected void onEvent(@NonNull PlayerEvent event) {
        if (event instanceof FullscreenEvents.Toggle) {
            // Handle fullscreen toggle logic
            toggleFullscreen();
        }
    }

    private void toggleFullscreen() {
        // Pure logic handling: modify Activity orientation, adjust system UI, etc.
    }
}

Implementation Methods:

Method

Description

Applicable Scenarios

Inherit BaseSlot, getLayoutId() returns 0

Simple, inherits FrameLayout but does not render.

Most headless slots.

Implement ISlot interface + compose SlotBehavior

Flexible, does not inherit View at all.

Pure logic components.

Design Value:

Headless slots deliver the same advantages of the slot system to business logic:

  • Unified lifecycle management.

  • Automatic synchronization with player status.

  • Pluggable replacement.

  • Equal collaboration with UI slots.

Best practices

Lifecycle Management

public class MySlot extends BaseSlot {

    @Override
    public void onAttach(@NonNull SlotHost host) {
        super.onAttach(host);
        // Initialize view
    }

    @Override
    public void onDetach() {
        // Clean up resources
        super.onDetach();
    }
}

Event Subscription

@Override
protected List<Class<? extends PlayerEvent>> observedEvents() {
    return Arrays.asList(PlayerEvents.StateChanged.class);
}

@Override
protected void onEvent(@NonNull PlayerEvent event) {
    if (event instanceof PlayerEvents.StateChanged) {
        // Handle state changes
    }
}

Surface Selection

Scenario

Recommended Type

Reason

Normal playback

SurfaceView

Better performance

Requires animation

TextureView

Supports transformations

Background playback

No Surface

Saves resources

Example Reference

The project provides a complete example located at playerkit-examples/example-slot-system.

Example Features

Feature

Description

SurfaceView switching

Suitable for normal playback scenarios

TextureView switching

Suitable for scenarios requiring animation

Empty slot switching

Suitable for audio-only playback

Run the Example

In the Demo App, select the Slot System example to see the effect.

API Reference

Core Interfaces

Interface/Class

Description

ISlot

Slot interface, defines lifecycle

BaseSlot

Slot base class, encapsulates common logic

SlotRegistry

Service registry, manages builders

ISlotManager

Manager, provides rebuild and query functions

BaseSlot Methods

Method

Description

getLayoutId()

Returns the layout resource ID

getHost()

Gets the slot host

show() / hide() / gone()

Controls visibility

postEvent(event)

Publishes events

Technical Principles

Lifecycle

Slots use a dual lifecycle system:

Lifecycle

Description

View lifecycle

onAttach → onDetach

Data lifecycle

onBindData → onUnbindData

Relationship with Android Lifecycle:

The slot lifecycle is independent of the Android Activity or Fragment lifecycle. To perform specific operations (such as pausing animations or stopping polling) when the app switches between foreground and background, handle it as follows:

Method One: Listen for Playback State Events

Listen for playback state changes using observedEvents(). Stop animations when the player pauses:

@Override
protected List<Class<? extends PlayerEvent>> observedEvents() {
    return Arrays.asList(PlayerEvents.StateChanged.class);
}

@Override
protected void onEvent(@NonNull PlayerEvent event) {
    if (event instanceof PlayerEvents.StateChanged) {
        PlayerState state = ((PlayerEvents.StateChanged) event).newState;
        if (state == PlayerState.PAUSED || state == PlayerState.STOPPED) {
            stopAnimation();  // Pause animation
        } else if (state == PlayerState.PLAYING) {
            startAnimation();  // Resume animation
        }
    }
}

Method Two: Control in Activity/Fragment

In the Activity’s onPause() or onResume() methods, retrieve the slot through the slot manager and call its methods:

@Override
protected void onPause() {
    super.onPause();
    MyAnimationSlot slot = playerView.getSlotManager().getSlot(SlotType.CUSTOM);
    if (slot != null) {
        slot.pauseAnimation();
    }
}

@Override
protected void onResume() {
    super.onResume();
    MyAnimationSlot slot = playerView.getSlotManager().getSlot(SlotType.CUSTOM);
    if (slot != null) {
        slot.resumeAnimation();
    }
}

Unidirectional Data Flow

The Slot System uses a unidirectional data flow architecture. Slots do not hold direct references to controllers. Instead, they interact in a decoupled manner through interfaces provided by the host:

Controller → State / Event → Slot (State flows down, read-only)
Slot → Command → Controller (Commands flow up, send-only)
  • State flows down: Two ways to obtain the state.

    • Active query: Query the current state using getPlayerStateStore().

    • Passive listening: Receive state change notifications by subscribing to events.

  • Commands flow up: Slots send commands using postEvent(). The controller executes them, and the slot does not depend on execution details.

Actively query state: Get read-only access to the player state using host.getPlayerStateStore():

@Override
public void onAttach(@NonNull SlotHost host) {
    super.onAttach(host);

    // Query playback state
    PlayerState state = host.getPlayerStateStore().getPlayState();

    // Query current playback position and total duration
    long position = host.getPlayerStateStore().getCurrentPosition();
    long duration = host.getPlayerStateStore().getDuration();
}

Passively listen for state: Receive state changes by subscribing to events:

@Override
protected List<Class<? extends PlayerEvent>> observedEvents() {
    return Arrays.asList(
        PlayerEvents.StateChanged.class,    // Playback state changes
        PlayerEvents.Info.class             // Playback progress updates
    );
}

@Override
protected void onEvent(@NonNull PlayerEvent event) {
    if (event instanceof PlayerEvents.StateChanged) {
        // Handle playback state changes
        updatePlayPauseIcon(((PlayerEvents.StateChanged) event).newState);
    } else if (event instanceof PlayerEvents.Info) {
        // Handle playback progress updates
        PlayerEvents.Info info = (PlayerEvents.Info) event;
        updateProgress(info.currentPosition, info.bufferedPosition, info.duration);
    }
}

Send commands: Send command events using postEvent() to trigger player behavior:

// Toggle play/pause
postEvent(new PlayerCommand.Toggle(mPlayerId));

// Seek to a specific position
postEvent(new PlayerCommand.Seek(mPlayerId, 30000));

// Set playback speed
postEvent(new PlayerCommand.SetSpeed(mPlayerId, 1.5f));

// Take a screenshot
postEvent(new PlayerCommand.Snapshot(mPlayerId));

The unidirectional data flow architecture completely decouples slots from controllers. Slots do not need to hold controller references. They only need to focus on querying state and sending commands, achieving clear separation of concerns.

Horizontal Isolation and Event Specification Between Slots

To ensure architectural stability, slots must not directly obtain instances of other slots or engage in point-to-point communication.

When Slot A (such as a settings overlay slot) triggers a state change that affects Slot B (such as a bottom control bar slot):

  1. Event bubbling: Slot A uses postEvent() to pass the event to the Controller.

  2. State sinking: The Controller or StateStore centrally handles state changes and broadcasts the new state to all listening slots.

This U-shaped link (**event bubbling, state sinking**) avoids mesh coupling and deadlock risks.

Event interception tip: Slot containers covering the page’s top layer must handle gesture event dispatch to avoid blocking gesture detection by the underlying GestureControlSlot.

FAQ

When is onAttach called?

It triggers when you call playerView.attach() or slotManager.rebuildSlots().

Custom Slot Considerations?

  1. Call super.onAttach(host) in onAttach.

  2. Clean up resources before onDetach.

How to Debug?

Use LogHub to view logs. The TAG format is ClassName.BaseSlot.

Common Crash Examples

The following are the most common crash-inducing issues reported by customers. Avoid them strictly:

Error 1: Not Calling super.onAttach() Leads to Lifecycle Disruption

Incorrect code:

public class MySlot extends BaseSlot {

    @Override
    public void onAttach(@NonNull SlotHost host) {
        // Forgot to call super.onAttach(host)
        // Directly initialize the view
        ImageView iv = findViewByIdCompat(R.id.iv_icon);
        iv.setOnClickListener(v -> postEvent(...));  // NullPointerException!
    }
}    

Correct code:

@Override
public void onAttach(@NonNull SlotHost host) {
    super.onAttach(host);  // Must be called first
    // Then initialize the view
    ImageView iv = findViewByIdCompat(R.id.iv_icon);
    iv.setOnClickListener(v -> postEvent(...));
}          

Cause of crash: If super.onAttach(host) is not called, slotBehavior cannot initialize, events cannot be subscribed, and Surface cannot be set. This causes getHost() to return null, postEvent() to fail, and event subscriptions to become invalid.

Error 2: ID Overwrite During Custom XML Leads to NullPointerException

Incorrect code:

<!-- When customizing the layout, the ID conflicts with the official default ID -->
<LinearLayout ...>
    <!-- The official uses @+id/iv_back, and you overwrote it -->
    <ImageView
        android:id="@+id/iv_back"
        android:src="@drawable/my_icon" />  <!-- The official expectation is a back button -->
</LinearLayout>      

Correct practice:

  1. Keep core control IDs unchanged: Control IDs used by the Slot class must be retained in the layout.

  2. Control type matching: The control type corresponding to the ID must match the type in the code.

<!-- Keep core ID unchanged -->
<LinearLayout ...>
    <ImageView
        android:id="@+id/iv_back"  <!-- Keep the ID, but you can change the style -->
        android:layout_width="48dp"
        android:layout_height="48dp"
        android:src="@drawable/custom_back" />  <!-- You can change the icon -->
</LinearLayout>   

Cause of crash: If the layout lacks the control ID required by the Slot class (R.id.iv_back) or the control type is incorrect, findViewByIdCompat() causes a failed cast or incorrect click event binding.

Error 3: Calling getHost() in the Constructor

Incorrect code:

public class MySlot extends BaseSlot {

    public MySlot(@NonNull Context context) {
        super(context);
        // getHost() returns null in the constructor
        SlotHost host = getHost();  // null!
    }
}

Correct code:

@Override
public void onAttach(@NonNull SlotHost host) {
    super.onAttach(host);
    // Host can only be accessed after onAttach
    SlotHost host = getHost();  // Obtained normally
}

Cause of crash: The slot instance is not yet attached to the host during construction. This causes getHost() to return null.

Error 4: Not Cleaning Up Resources in onDetach Leads to Memory Leak

Incorrect code:

public class MySlot extends BaseSlot {

    private Handler handler = new Handler();

    @Override
    public void onAttach(@NonNull SlotHost host) {
        super.onAttach(host);
        handler.postDelayed(() -> updateUI(), 1000);  // Delayed task
    }

    @Override
    public void onDetach() {
        // Forgot to remove the delayed task
        super.onDetach();
    }
}

Correct code:

@Override
public void onDetach() {
    handler.removeCallbacksAndMessages(null);  // Clean up all delayed tasks
    super.onDetach();
}

Cause of crash: If a delayed task holds a Slot reference, memory cannot be reclaimed (memory leak). Also, the Slot might already be in a detached state when the task executes.

Error 5: Event Subscription Not Declared in observedEvents()

Incorrect code:

public class MySlot extends BaseSlot {

    @Override
    public void onAttach(@NonNull SlotHost host) {
        super.onAttach(host);
        // Expected to receive events, but not declared in observedEvents()
    }

    @Override
    protected void onEvent(@NonNull PlayerEvent event) {
        // Never called!
    }
}

Correct code:

@Override
protected List<Class<? extends PlayerEvent>> observedEvents() {
    return Arrays.asList(
        PlayerEvents.StateChanged.class,  // Declare events to subscribe to
        PlayerEvents.Info.class
    );
}

@Override
protected void onEvent(@NonNull PlayerEvent event) {
    // Now events can be received normally
}

Cause of crash: BaseSlot declares events to subscribe to via observedEvents(). If not declared, it will not subscribe.