From Legacy Versions to v1.34.2+
This guide helps you migrate your existing Supabase UE5 Plugin implementation to the latest production-ready architecture featuring subsystem-based design, improved memory management, and enhanced thread safety.
| Area | Legacy Approach | New Architecture |
|---|---|---|
| Core System | Direct SupabaseClient usage | USupabaseSubsystem (GameInstance scoped) |
| Access Pattern | Manual client instantiation | USupabaseManager static interface |
| Memory Management | Manual cleanup required | Automatic lifecycle management |
| Thread Safety | Limited thread safety | Full thread-safe operations |
| Connection Management | Per-operation connections | Centralized connection pooling |
| Error Handling | Basic error reporting | Comprehensive error recovery |
| Session Management | Manual session handling | Automatic session persistence |
// Old initialization pattern
void AMyGameMode::BeginPlay()
{
Super::BeginPlay();
// Manual client creation
SupabaseClient = NewObject<USupabaseClient>();
// Manual connection setup
USupabaseConnection* Connection = NewObject<USupabaseConnection>();
Connection->SupabaseServerUrl = TEXT("https://your-project.supabase.co");
Connection->SupabaseCredentials.AnonymousKey = TEXT("your-key");
SupabaseClient->SetSupabaseConnection(Connection);
}
// New subsystem-based initialization
void AMyGameInstance::BeginPlay()
{
Super::BeginPlay();
// Create connection configuration
USupabaseConnection* Connection = NewObject<USupabaseConnection>();
Connection->SupabaseServerUrl = TEXT("https://your-project.supabase.co");
Connection->SupabaseCredentials.AnonymousKey = TEXT("your-anon-key");
Connection->ConnectionName = TEXT("Main");
// Initialize through subsystem (handles everything automatically)
USupabaseManager::InitializeSupabase(this, Connection);
// Optional: Bind to subsystem events
USupabaseSubsystem* Subsystem = USupabaseManager::GetSupabaseSubsystem(this);
if (Subsystem)
{
Subsystem->OnConnectionStateChanged.AddDynamic(this, &AMyGameInstance::OnConnectionChanged);
Subsystem->OnAuthenticationStateChanged.AddDynamic(this, &AMyGameInstance::OnAuthChanged);
}
}
// Old direct client usage
void AMyPlayerController::LoginUser()
{
if (SupabaseClient)
{
SupabaseClient->SupabaseLoginSuccessful.AddDynamic(this, &AMyPlayerController::OnLoginSuccess);
SupabaseClient->SupabaseRequestFailed.AddDynamic(this, &AMyPlayerController::OnLoginFailed);
SupabaseClient->LoginWithEmail(Email, Password);
}
}
// New manager-based approach
void AMyPlayerController::LoginUser()
{
// Simple static method - handles all complexity internally
USupabaseManager::LoginWithEmail(this, Email, Password);
// Or use subsystem directly for custom event handling
USupabaseSubsystem* Subsystem = USupabaseManager::GetSupabaseSubsystem(this);
if (Subsystem)
{
Subsystem->OnLoginSuccessful.AddDynamic(this, &AMyPlayerController::OnLoginSuccess);
Subsystem->OnLoginFailed.AddDynamic(this, &AMyPlayerController::OnLoginFailed);
Subsystem->LoginWithEmail(Email, Password);
}
}
// Old async query pattern
void AMyActor::QueryData()
{
UAsyncQuery* QueryTask = NewObject<UAsyncQuery>();
QueryTask->Connection = MyConnection; // Manual connection management
QueryTask->TableName = TEXT("users");
QueryTask->Columns = TEXT("*");
QueryTask->OnSuccess.AddDynamic(this, &AMyActor::OnQuerySuccess);
QueryTask->OnFailure.AddDynamic(this, &AMyActor::OnQueryFailure);
QueryTask->Activate();
}
// New subsystem-aware async operations
void AMyActor::QueryData()
{
// Uses subsystem automatically - no connection management needed
UAsyncQuery* QueryTask = UAsyncQuery::QueryTableAsync(
this, // World context
TEXT("users"), // Table
TEXT("*") // Columns
// Connection automatically retrieved from subsystem
);
QueryTask->OnSuccess.AddDynamic(this, &AMyActor::OnQuerySuccess);
QueryTask->OnFailure.AddDynamic(this, &AMyActor::OnQueryFailure);
QueryTask->Activate();
}
// Old manual websocket management
void AMyActor::SubscribeToChanges()
{
UAsyncRealtime* RealtimeTask = NewObject<UAsyncRealtime>();
RealtimeTask->Connection = MyConnection; // Manual connection
RealtimeTask->Table = TEXT("game_events");
RealtimeTask->Schema = TEXT("public");
// Manual cleanup required
RealtimeTask->OnInsert.AddDynamic(this, &AMyActor::OnDataInserted);
RealtimeTask->Activate();
}
// New thread-safe real-time with automatic cleanup
void AMyActor::SubscribeToChanges()
{
UAsyncRealtime* RealtimeTask = UAsyncRealtime::ListenToTable(
this, // World context
TEXT("game_events"), // Table
TEXT("public") // Schema
// Connection and cleanup handled automatically
);
RealtimeTask->OnInsert.AddDynamic(this, &AMyActor::OnDataInserted);
RealtimeTask->OnUpdate.AddDynamic(this, &AMyActor::OnDataUpdated);
RealtimeTask->OnDelete.AddDynamic(this, &AMyActor::OnDataDeleted);
RealtimeTask->Activate();
}
USupabaseSubsystemUSupabaseManager provides static access methodsUSupabaseManager::InitializeSupabase() instead of direct client setupUSupabaseClient to USupabaseSubsystem// Add required modules for subsystem support
PublicDependencyModuleNames.AddRange(new string[]
{
"Core",
"CoreUObject",
"Engine",
"HTTP",
"Json",
"JsonUtilities",
"WebSockets",
"WebBrowserWidget" // Required for enhanced auth
});
// Remove old includes
#include "SupabaseClient.h" // Direct client usage
// Add new includes
#include "SupabaseManager.h" // Static manager interface
#include "SupabaseSubsystem.h" // Subsystem access (if needed)
// Remove from header files
UPROPERTY()
USupabaseClient* SupabaseClient;
UPROPERTY()
USupabaseConnection* Connection;
// These are now managed automatically by the subsystem
// Old: Bind to client events
SupabaseClient -> On Login Successful
// New: Bind to subsystem events
Get Supabase Subsystem -> On Login Successful
// Or use manager convenience methods
Get Supabase Manager -> Login With Email
// Old pattern
UAsyncQuery* Query = NewObject<UAsyncQuery>();
Query->Connection = MyConnection;
Query->TableName = TEXT("users");
// New pattern
UAsyncQuery* Query = UAsyncQuery::QueryTableAsync(
this, // World context
TEXT("users"), // Table name
TEXT("*") // Columns
// Connection retrieved automatically
);
// Old pattern
UAsyncInsertRow* Insert = NewObject<UAsyncInsertRow>();
Insert->Connection = MyConnection;
Insert->TableName = TEXT("users");
Insert->JsonData = MyJsonData;
// New pattern
UAsyncInsertRow* Insert = UAsyncInsertRow::InsertRowAsync(
this, // World context
TEXT("users"), // Table name
MyJsonData // Data to insert
// Connection handled automatically
);
void AMyActor::OnSupabaseError(const FString& Error)
{
// New error helper methods
if (USupabaseManager::IsNetworkError(Error))
{
// Handle network connectivity issues
UE_LOG(LogTemp, Warning, TEXT("Network connectivity issue: %s"), *Error);
}
else if (USupabaseManager::IsAuthenticationError(Error))
{
// Handle authentication failures
UE_LOG(LogTemp, Warning, TEXT("Authentication issue: %s"), *Error);
// Automatic retry handled by subsystem
}
}
// Legacy code may fail if client not properly initialized
if (SupabaseClient)
{
SupabaseClient->LoginWithEmail(Email, Password); // May be null
}
// Use manager which handles null checks internally
USupabaseManager::LoginWithEmail(this, Email, Password);
// Or check subsystem availability
if (USupabaseManager::IsSubsystemAvailable(this))
{
USupabaseSubsystem* Subsystem = USupabaseManager::GetSupabaseSubsystem(this);
Subsystem->LoginWithEmail(Email, Password);
}
// Old async operations may not cleanup properly
UAsyncQuery* Query = NewObject<UAsyncQuery>();
// If not properly cleaned up, causes memory leaks
// New async operations handle cleanup automatically
UAsyncQuery* Query = UAsyncQuery::QueryTableAsync(this, TEXT("users"), TEXT("*"));
// Cleanup handled in BeginDestroy() and SafeCleanup()
// Old real-time operations may cause thread safety issues
void OnRealtimeMessage()
{
// Direct UI updates from WebSocket thread - UNSAFE
UpdateUI();
}
// New real-time operations are thread-safe
void OnRealtimeMessage(const FString& EventType, const FString& Payload)
{
// Automatically executed on game thread - SAFE
UpdateUI();
}
// Manual session management required
void SaveUserSession()
{
// Custom save logic required
FString AccessToken = SupabaseClient->GetAccessToken();
// Manual save to disk/preferences
}
// Automatic session persistence
void AMyGameInstance::BeginPlay()
{
Super::BeginPlay();
// Sessions automatically saved/restored
USupabaseManager::InitializeSupabase(this, Connection);
// Previous session restored automatically if valid
}
Supabase.Build.cs with new dependenciesUSupabaseClient usage with USupabaseManagerUSupabaseManager interface for all operationsThis migration guide is updated with each major release. For version-specific migration notes, check the release notes for your target version.