Skip to main content
Logo
Explore APIsContact Us
  • Home
  1. Resources
  2. Virtual Stadium
  3. Login and User State Tracking

Login and User State Tracking

#Login Manager

With LoginManager we manage login operations within the Virtual Stadium Data SDK. This interface provides the necessary functionalities to perform user authentication through JWT token validation and to manage user sessions by handling login and logout operations.

The LoginManager plays a crucial role in ensuring that users are authenticated before they can interact with the SDK's features. It uses JWT tokens, which are compact and secure, for authenticating users. This method of authentication is essential for maintaining the security of user data and for providing a seamless user experience across different components of the SDK.

Functions:

  • login(jwtToken: String): Authenticates the user with the provided JWT token. This is a prerequisit for further interactions with the SDK's functionalities.
  • logOut(): Performs the logout operation, effectively ending the user's session.
  • loginState: A common flow exposed to observe the current login state, allowing other components of the SDK to react to changes in the user's authentication status.

#Android Implementation

#ViewModel Example

kotlin
import ag.sportradar.virtualstadium.datasdk.services.LoginManager
import androidx.lifecycle.ViewModel
import org.koin.core.component.KoinComponent
import org.koin.core.component.get

class LoginViewModel :
    ViewModel(),
    KoinComponent {

    private val loginManager: LoginManager
        get() = get()

    val loginState = loginManager.loginState

    fun login(jwtToken: String) {
        loginManager.login(jwtToken)
    }

    fun logout() {
        loginManager.logOut()
    }
}

#Login Screen Example

kotlin
@Composable
fun LoginScreen(
    modifier: Modifier = Modifier,
    loginViewModel: LoginViewModel,
) {

    val loginState by loginViewModel.loginState.collectAsStateWithLifecycle()

    Scaffold { paddingValues ->
        when (loginState.loginStatus) {
            LoginStatus.LOGGED_OUT -> {
                Box(
                    modifier = Modifier
                        .padding(paddingValues)
                        .fillMaxSize(),
                    contentAlignment = Alignment.BottomCenter,
                ) {
                    OutlinedButton(
                        onClick = { loginVm.login() },
                    ) {
                        Text("Log In")
                    }
                }
            }

            LoginStatus.LOADING -> {
                Box(
                    modifier = Modifier
                        .padding(paddingValues)
                        .fillMaxSize(),
                    contentAlignment = Alignment.Center,
                ) {
                    CircularProgressIndicator()
                }
            }

            LoginStatus.LOGGED_IN -> {
                ExampleScreen(loginViewModel = loginVm)
            }

            LoginStatus.ERROR -> {
                Column(
                    modifier = Modifier
                        .padding(paddingValues)
                        .fillMaxSize(),
                    verticalArrangement = Arrangement.Center,
                ) {
                    Text("SDK not initialized!. Error: ${loginState.loginError?.message}")
                    OutlinedButton(
                        onClick = { loginVm.login() },
                    ) {
                        Text("Try to log in again")
                    }
                }
            }
        }
    }
}

#iOS Implementation

#ViewModel Example

swift
import VirtualStadiumDataSDK

class ExampleLoginViewModel: ObservableObject {

    private var loginManager = KoinHelper().getLoginManager()
    @Published var loginStatus: LoginStatus? = .loggedOut
    @Published var loginError: LoginError?

    init() {
        subscribeToLoginState()
    }

    private func subscribeToLoginState() {
        loginManager.loginState.subscribe { [weak self] loginState in
            self?.loginStatus = loginState?.loginStatus
            self?.loginError = loginState?.loginError
        }
    }

    func login() {
        loginManager.login(jwtToken: "YOUR_JWT_TOKEN")
    }

    func logout() {
        loginManager.logOut()
    }
}

loginState is of type CommonStateFlow and can be subscribed to. We are notified of any changes to the state.

#Login Screen Example

#SwiftUI
swift
import SwiftUI

struct LoginScreen: View {

    @ObservedObject var loginViewModel: ExampleLoginViewModel = ExampleLoginViewModel()

    var body: some View {
        switch loginViewModel.loginStatus {
        case .loggedOut:
            VStack {
                Spacer()
                Button("Log in",  action: {
                    loginViewModel.login()
                })
                .padding(.bottom, 40)
            }

        case .loading:
            VStack {
                ProgressView()
            }

        case .loggedIn:
            LoggedInView()

        case .error:
            ErrorScreen(errorText: "Error: \n\(loginViewModel.loginError?.description() ?? "Unknown error")")

        default:
            ErrorScreen(errorText: "Something went wrong!")
        }
    }
}
#UIKit
swift
import UIKit
import Combine

class LoginViewController: UIViewController {

    private var loginViewModel = ExampleLoginViewModel()
    private var cancellables: Set<AnyCancellable> = []

    private let loginButton: UIButton = {
        let button = UIButton(type: .system)
        button.setTitle("Log in", for: .normal)
        button.addTarget(nil, action: #selector(loginButtonTapped), for: .touchUpInside)
        return button
    }()

    private let progressView: UIActivityIndicatorView = {
        let activityIndicator = UIActivityIndicatorView(style: .large)
        activityIndicator.hidesWhenStopped = true
        return activityIndicator
    }()

    private let errorLabel: UILabel = {
        let label = UILabel()
        label.numberOfLines = 0
        label.textAlignment = .center
        return label
    }()

    override func viewDidLoad() {
        super.viewDidLoad()

        setupViews()
        observeViewModel()
        updateUI()
    }

    private func setupViews() {
        view.addSubview(loginButton)
        view.addSubview(progressView)
        view.addSubview(errorLabel)

        loginButton.translatesAutoresizingMaskIntoConstraints = false
        progressView.translatesAutoresizingMaskIntoConstraints = false
        errorLabel.translatesAutoresizingMaskIntoConstraints = false

        NSLayoutConstraint.activate([
            loginButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            loginButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -40),

            progressView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            progressView.centerYAnchor.constraint(equalTo: view.centerYAnchor),

            errorLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            errorLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            errorLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor)
        ])
    }

    private func observeViewModel() {
        loginViewModel.$loginStatus
            .receive(on: DispatchQueue.main)
            .sink { [weak self] _ in
                self?.updateUI()
            }
            .store(in: &cancellables)

        loginViewModel.$loginError
            .receive(on: DispatchQueue.main)
            .sink { [weak self] _ in
                self?.updateUI()
            }
            .store(in: &cancellables)
    }

    private func updateUI() {
        switch loginViewModel.loginStatus {
        case .loggedOut:
            loginButton.isHidden = false
            progressView.stopAnimating()
            errorLabel.isHidden = true

        case .loading:
            loginButton.isHidden = true
            progressView.startAnimating()
            errorLabel.isHidden = true

        case .loggedIn:
            loginButton.isHidden = true
            progressView.stopAnimating()
            errorLabel.isHidden = true

        case .error:
            loginButton.isHidden = true
            progressView.stopAnimating()
            errorLabel.text = "Error: \n\(loginViewModel.loginError?.description() ?? "Unknown error")"
            errorLabel.isHidden = false

        default:
            loginButton.isHidden = true
            progressView.stopAnimating()
            errorLabel.text = "Something went wrong!"
            errorLabel.isHidden = false
        }
    }

    @objc private func loginButtonTapped() {
        loginViewModel.login()
    }
}

#User Status

The UserProvider interface is a crucial component of the Virtual Stadium Data SDK, designed to manage and maintain user-related data and state. It encapsulates functionalities essential for handling user data, managing user states, and dealing with user infractions within the application.

Key functionalities include:

  • Observing and updating user state changes.
  • Managing user infractions, including adding, retrieving, and clearing infractions.
  • Handling user data based on JWT tokens for authentication and authorization purposes.
  • Providing mechanisms for user timeout and ban management, allowing for a responsive and controlled user experience.

The UserProvider interface is utilized across the SDK to ensure that user-related operations are performed in a secure, efficient, and consistent manner. It plays a pivotal role in maintaining the integrity of the user experience, enforcing rules and policies, and facilitating a safe environment for users to interact with the application's features.

#User State

To detect state changes of current users status, you must collect changes from the SDK's UserProvider. Use provider has a class called UserState, that contains values for the user and userStatus:

  • UserStatus.Initial: This is the initial value for users status.
  • UserStatus.Valid: User can post messages.
  • UserStatus.Timeout(remainingSeconds: Long): User has been temporarily banned for a certain duration, represented by remainingSeconds (in seconds). Remaining seconds value updates every second.
  • UserStatus.Banned: User is banned and can't post messages.

#Android Implementation

kotlin
internal class LoginViewModel :
    ViewModel(),
    KoinComponent {

    ...

    private val userProvider: UserProvider
        get() = get()

    ...

    // Represents the current state of the user
    val userState = userProvider.userState

    // Indicates which is the next infraction for the user
    val nextInfraction = userProvider.nextInfraction

    // Countdown timer for dismissing the infraction
    val dismissInfraction = userProvider.dismissTimerCountDown

    fun startDismissInfractionJob() {
        userProvider.startAutoDismissInfraction()
    }

    fun stopDismissInfractionJob() {
        userProvider.stopAutoDismissInfraction()
    }

    fun getNextInfraction() {
        userProvider.getNextInfraction()
    }

    fun removeFirstInfraction() {
        userProvider.removeFirstInfraction()
    }

    ...
}

All those variables are type CommonStateFlow which can be collected in the UI, like the loginState in the Login Screen Example

#iOS Implementation

swift
import VirtualStadiumDataSDK

class ExampleUserViewModel: ObservableObject {

    private var userProvider = KoinHelper().getUserProvider()

    @Published var userState: UserState?
    @Published var nextInfraction: InfractionMessage?
    @Published var dismissTimerCountDown: Int?

    init() {
        subscribeToUserProvider()
    }

    private func subscribeToUserProvider() {
        userProvider.userState.subscribe { [weak self] userState in
            self?.userState = userState
        }
        userProvider.nextInfraction.subscribe { [weak self] nextInfraction in
            self?.nextInfraction = nextInfraction
        }
        userProvider.dismissTimerCountDown.subscribe { [weak self] dismissTimerCountDown in
            self?.dismissTimerCountDown = Int(truncating: dismissTimerCountDown ?? 0)
        }
    }

    func startDismissInfractionJob() {
        userProvider.startAutoDismissInfraction()
    }

    func stopDismissInfractionJob() {
        userProvider.stopAutoDismissInfraction()
    }

    func getNextInfraction() {
        userProvider.getNextInfraction()
    }

    func removeFirstInfraction() {
        userProvider.removeFirstInfraction()
    }
}

All these variables are of type CommonStateFlow, which can be subscribed to, similar to loginState in the Login Screen Example

Last updated 13 days ago
Is this site helpful?
Virtual Stadium, Moderation, Engagement Tools
On this page
  • Login Manager
  • Android Implementation
  • iOS Implementation
  • User Status
  • User State
  • Android Implementation
  • iOS Implementation