Static Access Layer for Simplified Supabase Integration
The USupabaseManager implements a static access layer pattern that provides a simplified, Blueprint-friendly interface to the underlying USupabaseSubsystem. This design pattern eliminates the complexity of subsystem management while maintaining all the benefits of the centralized architecture.
UCLASS(BlueprintType)
class SUPABASE_API USupabaseManager : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
// Static interface methods
UFUNCTION(BlueprintCallable, Category = "Supabase|Manager",
meta = (WorldContext = "WorldContext"))
static void LoginWithEmail(const UObject* WorldContext,
const FString& Email,
const FString& Password);
// Implementation delegates to subsystem
// ...
};
| Principle | Implementation | Benefit |
|---|---|---|
| Static Interface | UBlueprintFunctionLibrary base class | No object instantiation required |
| World Context Aware | All methods accept WorldContext parameter | Automatic subsystem discovery |
| Error Resilient | Comprehensive validation and error handling | Graceful failure modes |
| Blueprint First | All methods exposed to Blueprint | Designer-friendly integration |
| Subsystem Delegation | All operations delegate to USupabaseSubsystem | Maintains centralized state |
UFUNCTION(BlueprintPure, Category = "Supabase|Manager",
meta = (WorldContext = "WorldContext"))
static USupabaseSubsystem* GetSupabaseSubsystem(const UObject* WorldContext);
UFUNCTION(BlueprintPure, Category = "Supabase|Manager",
meta = (WorldContext = "WorldContext"))
static USupabaseClient* GetSupabaseClient(const UObject* WorldContext);
UFUNCTION(BlueprintPure, Category = "Supabase|Manager",
meta = (WorldContext = "WorldContext"))
static USupabaseConnection* GetActiveConnection(const UObject* WorldContext);
Implementation Details:
USupabaseSubsystem* USupabaseManager::GetSupabaseSubsystem(const UObject* WorldContext)
{
UWorld* World = GetWorldFromContext(WorldContext);
if (!World)
{
return nullptr;
}
UGameInstance* GameInstance = World->GetGameInstance();
if (!GameInstance)
{
return nullptr;
}
return GameInstance->GetSubsystem<USupabaseSubsystem>();
}
The manager implements sophisticated world context resolution to work from any object:
UWorld* USupabaseManager::GetWorldFromContext(const UObject* WorldContext)
{
if (!WorldContext)
{
return nullptr;
}
// Try to get world directly
if (const UWorld* World = WorldContext->GetWorld())
{
return const_cast<UWorld*>(World);
}
// Try to cast to world
if (const UWorld* World = Cast<UWorld>(WorldContext))
{
return const_cast<UWorld*>(World);
}
// Try to get from actor
if (const AActor* Actor = Cast<AActor>(WorldContext))
{
return Actor->GetWorld();
}
// Try to get from component
if (const UActorComponent* Component = Cast<UActorComponent>(WorldContext))
{
return Component->GetWorld();
}
return nullptr;
}
UFUNCTION(BlueprintCallable, Category = "Supabase|Manager",
meta = (WorldContext = "WorldContext"))
static bool InitializeSupabase(const UObject* WorldContext, USupabaseConnection* Connection);
UFUNCTION(BlueprintCallable, Category = "Supabase|Manager",
meta = (WorldContext = "WorldContext"))
static void TestConnection(const UObject* WorldContext);
Implementation Pattern:
bool USupabaseManager::InitializeSupabase(const UObject* WorldContext, USupabaseConnection* Connection)
{
if (!ValidateConnection(Connection))
{
LogManagerError(TEXT("InitializeSupabase"), TEXT("Invalid connection provided"));
return false;
}
USupabaseSubsystem* Subsystem = GetSupabaseSubsystem(WorldContext);
if (!Subsystem)
{
LogManagerError(TEXT("InitializeSupabase"), TEXT("Subsystem not available"));
return false;
}
return Subsystem->InitializeConnection(Connection);
}
UFUNCTION(BlueprintCallable, Category = "Supabase|Manager",
meta = (WorldContext = "WorldContext"))
static void LoginWithEmail(const UObject* WorldContext, const FString& Email, const FString& Password);
UFUNCTION(BlueprintCallable, Category = "Supabase|Manager",
meta = (WorldContext = "WorldContext"))
static void RegisterWithEmail(const UObject* WorldContext, const FString& Email, const FString& Password);
UFUNCTION(BlueprintCallable, Category = "Supabase|Manager",
meta = (WorldContext = "WorldContext"))
static void Logout(const UObject* WorldContext);
UFUNCTION(BlueprintCallable, Category = "Supabase|Manager",
meta = (WorldContext = "WorldContext"))
static void RefreshToken(const UObject* WorldContext);
Usage Example:
// Simple one-line authentication
USupabaseManager::LoginWithEmail(this, TEXT("user@example.com"), TEXT("password"));
// No need to manage subsystem, events, or cleanup
// Everything is handled automatically
UFUNCTION(BlueprintPure, Category = "Supabase|Manager",
meta = (WorldContext = "WorldContext"))
static bool IsConnected(const UObject* WorldContext);
UFUNCTION(BlueprintPure, Category = "Supabase|Manager",
meta = (WorldContext = "WorldContext"))
static bool IsAuthenticated(const UObject* WorldContext);
UFUNCTION(BlueprintPure, Category = "Supabase|Manager",
meta = (WorldContext = "WorldContext"))
static FUser GetCurrentUser(const UObject* WorldContext);
UFUNCTION(BlueprintPure, Category = "Supabase|Manager",
meta = (WorldContext = "WorldContext"))
static FString GetConnectionStatus(const UObject* WorldContext);
Implementation with Null Safety:
bool USupabaseManager::IsConnected(const UObject* WorldContext)
{
USupabaseSubsystem* Subsystem = GetSupabaseSubsystem(WorldContext);
return Subsystem ? Subsystem->IsConnected() : false;
}
FUser USupabaseManager::GetCurrentUser(const UObject* WorldContext)
{
USupabaseSubsystem* Subsystem = GetSupabaseSubsystem(WorldContext);
return Subsystem ? Subsystem->GetCurrentUser() : FUser();
}
bool USupabaseManager::ValidateConnection(USupabaseConnection* Connection)
{
if (!Connection)
{
return false;
}
if (Connection->SupabaseServerUrl.IsEmpty())
{
UE_LOG(LogTemp, Error, TEXT("SupabaseManager: Connection validation failed - empty server URL"));
return false;
}
if (Connection->SupabaseCredentials.AnonymousKey.IsEmpty())
{
UE_LOG(LogTemp, Error, TEXT("SupabaseManager: Connection validation failed - empty anonymous key"));
return false;
}
// Validate URL format
if (!Connection->SupabaseServerUrl.StartsWith(TEXT("http://")) &&
!Connection->SupabaseServerUrl.StartsWith(TEXT("https://")))
{
UE_LOG(LogTemp, Error, TEXT("SupabaseManager: Connection validation failed - invalid URL format"));
return false;
}
return true;
}
bool USupabaseManager::ValidateWorldContext(const UObject* WorldContext)
{
return GetWorldFromContext(WorldContext) != nullptr;
}
UFUNCTION(BlueprintPure, Category = "Supabase|Manager")
static bool IsNetworkError(const FString& ErrorMessage);
UFUNCTION(BlueprintPure, Category = "Supabase|Manager")
static bool IsAuthenticationError(const FString& ErrorMessage);
UFUNCTION(BlueprintPure, Category = "Supabase|Manager")
static FString FormatErrorMessage(const FString& Context, const FString& Error);
Implementation:
bool USupabaseManager::IsNetworkError(const FString& ErrorMessage)
{
return ErrorMessage.Contains(TEXT("network")) ||
ErrorMessage.Contains(TEXT("timeout")) ||
ErrorMessage.Contains(TEXT("connection")) ||
ErrorMessage.Contains(TEXT("unreachable"));
}
bool USupabaseManager::IsAuthenticationError(const FString& ErrorMessage)
{
return ErrorMessage.Contains(TEXT("401")) ||
ErrorMessage.Contains(TEXT("unauthorized")) ||
ErrorMessage.Contains(TEXT("authentication")) ||
ErrorMessage.Contains(TEXT("invalid_token"));
}
FString USupabaseManager::FormatErrorMessage(const FString& Context, const FString& Error)
{
return FString::Printf(TEXT("[%s] %s"), *Context, *Error);
}
void USupabaseManager::LoginWithEmail(const UObject* WorldContext, const FString& Email, const FString& Password)
{
USupabaseSubsystem* Subsystem = GetSupabaseSubsystem(WorldContext);
if (Subsystem)
{
Subsystem->LoginWithEmail(Email, Password);
}
else
{
LogManagerError(TEXT("LoginWithEmail"), TEXT("Subsystem not available"));
}
}
void USupabaseManager::LogManagerError(const FString& Function, const FString& Error)
{
UE_LOG(LogTemp, Error, TEXT("SupabaseManager::%s: %s"), *Function, *Error);
}
// In GameInstance or GameMode
void AMyGameInstance::BeginPlay()
{
Super::BeginPlay();
// Create connection
USupabaseConnection* Connection = NewObject<USupabaseConnection>();
Connection->SupabaseServerUrl = TEXT("https://your-project.supabase.co");
Connection->SupabaseCredentials.AnonymousKey = TEXT("your-anon-key");
// Initialize with one line
if (USupabaseManager::InitializeSupabase(this, Connection))
{
UE_LOG(LogTemp, Log, TEXT("Supabase initialized successfully"));
}
}
// In Player Controller or UI Widget
void AMyPlayerController::PerformLogin()
{
// Simple one-line login
USupabaseManager::LoginWithEmail(this, UserEmail, UserPassword);
// To handle events, you still need to bind to subsystem
if (USupabaseSubsystem* Subsystem = USupabaseManager::GetSupabaseSubsystem(this))
{
Subsystem->OnLoginSuccessful.AddDynamic(this, &AMyPlayerController::OnLoginSuccess);
Subsystem->OnLoginFailed.AddDynamic(this, &AMyPlayerController::OnLoginFailed);
}
}
// In any class, anywhere
void AMyActor::CheckSupabaseStatus()
{
if (USupabaseManager::IsConnected(this))
{
if (USupabaseManager::IsAuthenticated(this))
{
FUser CurrentUser = USupabaseManager::GetCurrentUser(this);
UE_LOG(LogTemp, Log, TEXT("User: %s"), *CurrentUser.Email);
}
else
{
UE_LOG(LogTemp, Log, TEXT("Connected but not authenticated"));
}
}
else
{
UE_LOG(LogTemp, Warning, TEXT("Supabase not connected"));
}
}
Event BeginPlay
-> Create Supabase Connection
-> Set Server URL: "https://your-project.supabase.co"
-> Set Anonymous Key: "your-anon-key"
-> Initialize Supabase (Manager)
-> Branch (Success?)
-> True: Print String "Supabase Ready"
-> False: Print String "Supabase Failed"
// Login Flow
Login Button Clicked
-> Get Email/Password from UI
-> Login With Email (Manager)
// Status Check
Tick Event
-> Is Connected (Manager)
-> Branch (Connected?)
-> True: Is Authenticated (Manager)
-> Branch (Authenticated?)
-> True: Get Current User (Manager)
-> Update UI with User Info
-> False: Show Login Screen
-> False: Show Connection Error
// Smart Error Classification
On Login Failed Event
-> Is Authentication Error (Manager)
-> Branch (Auth Error?)
-> True: Show "Invalid credentials" message
-> False: Is Network Error (Manager)
-> Branch (Network Error?)
-> True: Show "Connection failed" message
-> False: Show "Unknown error" message
// Works from any context
class MYGAME_API UMyUtilityClass
{
public:
static void CheckSupabaseFromAnywhere(UObject* AnyObject)
{
// Manager automatically resolves context
bool bConnected = USupabaseManager::IsConnected(AnyObject);
// Works from Actor, Component, Widget, GameMode, etc.
}
};
void AMyActor::ConditionalSupabaseOperation()
{
// Check availability before operations
if (USupabaseManager::IsSubsystemAvailable(this))
{
if (USupabaseManager::IsConnected(this))
{
// Perform operation
USupabaseManager::TestConnection(this);
}
else
{
UE_LOG(LogTemp, Warning, TEXT("Supabase not connected"));
}
}
else
{
UE_LOG(LogTemp, Error, TEXT("Supabase subsystem not available"));
}
}
The Manager works seamlessly with async operations by providing automatic subsystem integration:
// Async operations automatically use Manager pattern
void AMyActor::QueryDatabase()
{
// No need to pass connection - Manager handles it
UAsyncQuery* QueryNode = UAsyncQuery::QueryTableAsync(
this, // WorldContext - Manager resolves subsystem
TEXT("users"), // Table name
TEXT("*") // Columns
);
QueryNode->OnSuccess.AddDynamic(this, &AMyActor::OnQuerySuccess);
QueryNode->OnFailure.AddDynamic(this, &AMyActor::OnQueryFailure);
QueryNode->Activate();
}
void AMyActor::SubscribeToChanges()
{
// Manager pattern integrated into async operations
UAsyncRealtime* RealtimeNode = UAsyncRealtime::ListenToTable(
this, // WorldContext
TEXT("game_events"), // Table
TEXT("public") // Schema
);
// Manager automatically provides connection from subsystem
RealtimeNode->OnInsert.AddDynamic(this, &AMyActor::OnDataInserted);
RealtimeNode->Activate();
}
// Old pattern - multiple object creation
USupabaseClient* Client = NewObject<USupabaseClient>();
USupabaseConnection* Connection = NewObject<USupabaseConnection>();
// ... setup and configuration ...
// New pattern - single static call
USupabaseManager::LoginWithEmail(this, Email, Password);
// Manager caches subsystem lookups internally
// Multiple calls in same frame reuse cached reference
bool bConnected = USupabaseManager::IsConnected(this);
bool bAuthenticated = USupabaseManager::IsAuthenticated(this);
FUser User = USupabaseManager::GetCurrentUser(this);
// Only one subsystem lookup performed
// Thread safety handled at subsystem level
// Manager simply delegates - no additional synchronization needed
AsyncTask(ENamedThreads::AnyThread, []()
{
// Safe to call from any thread
bool bConnected = USupabaseManager::IsConnected(SomeActor);
});
// Good - use specific context objects
void AMyPlayerController::Login()
{
USupabaseManager::LoginWithEmail(this, Email, Password);
}
// Better - use GameInstance for global operations
void AMyGameMode::InitializeSupabase()
{
USupabaseManager::InitializeSupabase(GetGameInstance(), Connection);
}
// Always validate context
void UMyWidget::PerformOperation()
{
if (IsValid(GetOwningPlayer()))
{
USupabaseManager::TestConnection(GetOwningPlayer());
}
}
void AMyActor::SafeSupabaseOperation()
{
if (!USupabaseManager::IsSubsystemAvailable(this))
{
UE_LOG(LogTemp, Error, TEXT("Supabase not available"));
return;
}
// Proceed with operation
USupabaseManager::TestConnection(this);
}
void AMyActor::HandleSupabaseError(const FString& Error)
{
if (USupabaseManager::IsNetworkError(Error))
{
ShowNetworkErrorUI();
}
else if (USupabaseManager::IsAuthenticationError(Error))
{
ShowLoginScreen();
}
else
{
ShowGenericErrorUI(Error);
}
}
// Prefer Manager nodes over direct subsystem access
Get Supabase Manager -> Is Connected
// Instead of:
Get Game Instance -> Get Subsystem -> Is Connected
// Use Manager for operations, Subsystem for events
Login With Email (Manager)
Get Supabase Subsystem -> Bind Event to On Login Successful
UFUNCTION(BlueprintCallable, Category = "Supabase|Manager",
meta = (WorldContext = "WorldContext"))
static void EnableDebugLogging(const UObject* WorldContext, bool bEnable);
UFUNCTION(BlueprintPure, Category = "Supabase|Manager",
meta = (WorldContext = "WorldContext"))
static bool IsSubsystemAvailable(const UObject* WorldContext);
void AMyActor::DiagnoseSupabaseState()
{
UE_LOG(LogTemp, Log, TEXT("=== Supabase Manager Diagnostics ==="));
UE_LOG(LogTemp, Log, TEXT("Subsystem Available: %s"),
USupabaseManager::IsSubsystemAvailable(this) ? TEXT("Yes") : TEXT("No"));
UE_LOG(LogTemp, Log, TEXT("Connected: %s"),
USupabaseManager::IsConnected(this) ? TEXT("Yes") : TEXT("No"));
UE_LOG(LogTemp, Log, TEXT("Authenticated: %s"),
USupabaseManager::IsAuthenticated(this) ? TEXT("Yes") : TEXT("No"));
UE_LOG(LogTemp, Log, TEXT("Connection Status: %s"),
*USupabaseManager::GetConnectionStatus(this));
}
The Manager Pattern represents the evolution of Supabase integration from complex subsystem management to simple, one-line operations. It maintains all the benefits of the production-ready architecture while providing the simplicity that developers expect.