Let’s create a wrapper model that uses Ctx (the main context model):
src/model.rs
use serde::{Deserialize, Serialize};use stremio_core::{ models::ctx::Ctx, runtime::{Effect, Env, Model, Msg, Update},};#[derive(Clone, Debug, Serialize, Deserialize)]pub enum AppField { Ctx,}#[derive(Clone, Serialize)]pub struct App { pub ctx: Ctx,}impl App { pub fn new() -> Self { // Create a new context with default profile and empty collections let ctx = Ctx::new( Default::default(), // Profile Default::default(), // LibraryBucket Default::default(), // StreamsBucket Default::default(), // ServerUrlsBucket Default::default(), // NotificationsBucket Default::default(), // SearchHistoryBucket Default::default(), // DismissedEventsBucket ); App { ctx } }}impl<E: Env> Model<E> for App { type Field = AppField; fn update(&mut self, msg: &Msg) -> (Vec<Effect>, Vec<Self::Field>) { let (ctx_effects, _) = self.ctx.update(msg); let changed_fields = if ctx_effects.has_changed { vec![AppField::Ctx] } else { vec![] }; (ctx_effects.into_iter().collect(), changed_fields) } fn update_field( &mut self, msg: &Msg, field: &Self::Field, ) -> (Vec<Effect>, Vec<Self::Field>) { match field { AppField::Ctx => self.update(msg), } }}
The Model trait requires implementing update() and update_field() methods. These methods process messages and return effects along with changed fields.
Now let’s create the main application that initializes the runtime and handles events:
src/main.rs
mod env;mod model;use futures::StreamExt;use stremio_core::runtime::{ msg::{Action, ActionCtx}, Runtime, RuntimeAction, RuntimeEvent,};use stremio_core::types::api::AuthRequest;use crate::env::SimpleEnv;use crate::model::App;#[tokio::main]async fn main() { println!("Initializing Stremio Core application..."); // Step 1: Create the initial model let app = App::new(); println!("✓ Model created"); // Step 2: Initialize runtime with buffer size of 100 events let (runtime, mut rx) = Runtime::<SimpleEnv, App>::new( app, vec![], // Initial effects 100, // Event buffer size ); println!("✓ Runtime initialized"); // Step 3: Spawn event handler let event_handler = tokio::spawn(async move { let mut event_count = 0; while let Some(event) = rx.next().await { event_count += 1; match event { RuntimeEvent::NewState(fields, _) => { println!("[Event {}] State changed: {:?}", event_count, fields); } RuntimeEvent::CoreEvent(core_event) => { println!("[Event {}] Core event: {:?}", event_count, core_event); } } } println!("Event stream closed. Total events: {}", event_count); }); // Step 4: Read the current model state { let model = runtime.model().expect("Failed to read model"); println!("\n📊 Current state:"); println!(" - Authenticated: {}", model.ctx.profile.auth.is_some()); println!(" - Addons count: {}", model.ctx.profile.addons.len()); println!(" - Library items: {}", model.ctx.library.items.len()); } // Step 5: Dispatch some actions println!("\n🚀 Dispatching actions..."); // Example: Try to authenticate (will fail with our simple env, but demonstrates the flow) runtime.dispatch(RuntimeAction { field: None, action: Action::Ctx(ActionCtx::Authenticate(AuthRequest { type_field: "Login".to_string(), email: Some("[email protected]".to_string()), password: Some("password".to_string()), facebook: None, })), }); println!(" ✓ Dispatched authentication action"); // Give some time for events to process tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; // Step 6: Read the model state again { let model = runtime.model().expect("Failed to read model"); println!("\n📊 State after actions:"); println!(" - Context status: {:?}", model.ctx.status); } println!("\n✅ Application completed successfully!"); // Wait a bit for all events to be processed tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; // Note: In a real application, you'd keep the runtime alive // and continue dispatching actions based on user input drop(runtime); event_handler.await.unwrap();}
Create your initial model state (in this case, an App containing a Ctx)
2
Runtime Initialization
Initialize the Runtime with your model and environment type. This returns a runtime instance and an event receiver.
3
Event Handling
Spawn a task to process events from the runtime. You’ll receive RuntimeEvent::NewState when state changes and RuntimeEvent::CoreEvent for other events.
4
Dispatch Actions
Call runtime.dispatch() with RuntimeAction to trigger state changes. Actions flow through the update functions.
5
Effects Execution
The runtime automatically executes effects (HTTP requests, storage operations) returned from update functions.
6
State Updates
When effects complete or state changes, new events are emitted, creating a unidirectional data flow.