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.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()
}
}@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")
}
}
}
}
}
}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.
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!")
}
}
}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()
}
}
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:
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.
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.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
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