Retrieve a user’s details via a Portal.

Use case
This portal information can be used to provide a customized UI experience for the user. For example, you can show a thumbnail of the user in the application to indicate that they are currently logged in. Additionally, apps such as ArcGIS Field Maps use this functionality to integrate with Portal.
How to use the sample
Enter your ArcGIS Online credentials for the specified URL.
How it works
- On startup, the app presents the user with an editable text field containing a portal URL.
- Upon pressing the “Load” button, a portal will be created and loaded.
- If the portal is secured, it may potentially issue an authentication challenge.
- If the portal is successfully loaded, the info screen below will display the portal info, otherwise it will display the loading error.
- Upon successful login, get a
PortalUserusingportal.user. Get user attributes using:portalUser.portalNameportalUser.fullNameportalUser.emailportalUser.creationDateportalUser.thumbnail.image
- The “Sign out” button clears any saved credentials.
Relevant API
- OAuthUserConfiguration
- Portal
- PortalInfo
- PortalUser
About the data
This sample signs into your ArcGIS online account and displays the user’s profile information.
Additional information
This sample uses the toolkit’s authentication module to handle authentication. For information about setting up the toolkit, as well as code for the underlying component, visit the toolkit docs.
Tags
account, avatar, bio, cloud and portal, email, login, picture, profile, user, username
Sample Code
/* Copyright 2023 Esri * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */
package com.esri.arcgismaps.sample.showportaluserinfo
import android.os.Bundleimport androidx.activity.ComponentActivityimport androidx.activity.compose.setContentimport androidx.activity.enableEdgeToEdgeimport androidx.compose.material3.MaterialThemeimport androidx.compose.material3.Surfaceimport androidx.compose.runtime.Composableimport com.esri.arcgismaps.sample.sampleslib.theme.SampleAppThemeimport com.esri.arcgismaps.sample.showportaluserinfo.screens.MainScreen
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() setContent { SampleAppTheme { ShowPortalUserInfoApp() } } }
@Composable private fun ShowPortalUserInfoApp() { Surface( color = MaterialTheme.colorScheme.background ) { MainScreen( sampleName = getString(R.string.show_portal_user_info_app_name), application = application ) } }}/* * * Copyright 2023 Esri * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */
package com.esri.arcgismaps.sample.showportaluserinfo.components
import android.app.Applicationimport android.graphics.Bitmapimport android.graphics.BitmapFactoryimport androidx.lifecycle.AndroidViewModelimport androidx.lifecycle.viewModelScopeimport com.arcgismaps.httpcore.authentication.OAuthUserConfigurationimport com.arcgismaps.portal.Portalimport com.arcgismaps.toolkit.authentication.AuthenticatorStateimport com.esri.arcgismaps.sample.sampleslib.components.MessageDialogViewModelimport com.esri.arcgismaps.sample.showportaluserinfo.Rimport kotlinx.coroutines.flow.MutableStateFlowimport kotlinx.coroutines.flow.StateFlowimport kotlinx.coroutines.flow.asStateFlowimport kotlinx.coroutines.launchimport java.text.SimpleDateFormatimport java.util.Dateimport java.util.Locale
class AppViewModel(private val application: Application) : AndroidViewModel(application) {
val authenticatorState: AuthenticatorState = AuthenticatorState()
// create a ViewModel to handle dialog interactions as a public val val messageDialogVM: MessageDialogViewModel = MessageDialogViewModel()
private val noPortalInfoText = application.getString(R.string.no_portal_info) private val startInfoText = application.getString(R.string.start_info_text) private val arcGISUrl = application.getString(R.string.portal_url) private val oAuthUserConfiguration = OAuthUserConfiguration( arcGISUrl, // This client ID is for sample purposes only. For use of the Authenticator in your own app, // create your own client ID. For more info see: https://developers.arcgis.com/documentation/mapping-apis-and-services/security/tutorials/register-your-application/ application.getString(R.string.oauth_client_id), application.getString(R.string.oauth_redirect_uri) )
private val _portalUserName = MutableStateFlow(String()) val portalUserName: StateFlow<String> = _portalUserName.asStateFlow()
private val _emailID = MutableStateFlow(String()) val emailID: StateFlow<String> = _emailID.asStateFlow()
private val _userCreationDate = MutableStateFlow(String()) val userCreationDate: StateFlow<String> = _userCreationDate.asStateFlow()
private val _portalName = MutableStateFlow(String()) val portalName: StateFlow<String> = _portalName.asStateFlow()
private val defaultBitmap = BitmapFactory.decodeResource(application.resources, R.drawable.user)
private val _userThumbnail: MutableStateFlow<Bitmap> = MutableStateFlow(defaultBitmap) val userThumbnail: StateFlow<Bitmap> = _userThumbnail.asStateFlow()
private val _infoText: MutableStateFlow<String> = MutableStateFlow(startInfoText) val infoText: StateFlow<String> = _infoText.asStateFlow()
private val _isLoading: MutableStateFlow<Boolean> = MutableStateFlow(false) val isLoading: StateFlow<Boolean> = _isLoading.asStateFlow()
private val _url: MutableStateFlow<String> = MutableStateFlow(arcGISUrl) val url: StateFlow<String> = _url.asStateFlow() fun setUrl(newUrl: String) { _url.value = newUrl }
fun signOut() = viewModelScope.launch { _isLoading.value = true authenticatorState.signOut() _infoText.value = startInfoText _isLoading.value = false _portalUserName.value = "" _emailID.value = "" _userCreationDate.value = "" _portalName.value = "" _userThumbnail.value = defaultBitmap }
fun loadPortal() = viewModelScope.launch { _isLoading.value = true authenticatorState.oAuthUserConfigurations = listOf(oAuthUserConfiguration) val portal = Portal(url.value, Portal.Connection.Authenticated) portal.load().also { _isLoading.value = false }.onFailure { messageDialogVM.showMessageDialog(application.getString(R.string.load_portal_fail), it.message.toString()) }.onSuccess { portal.portalInfo?.apply { _portalUserName.value = this.user?.fullName ?: noPortalInfoText _emailID.value = this.user?.email ?: noPortalInfoText _portalName.value = this.portalName ?: noPortalInfoText // get the created date val date = Date.from(this.user?.creationDate) val dateFormat = SimpleDateFormat("dd-MMM-yyyy", Locale.US) _userCreationDate.value = dateFormat.format(date) this.user?.thumbnail?.load()?.onSuccess { _userThumbnail.value = this.user?.thumbnail?.image?.bitmap ?: defaultBitmap } } _infoText.value = application.getString(R.string.load_portal_success) } }}/* Copyright 2023 Esri * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */
package com.esri.arcgismaps.sample.showportaluserinfo.screens
import android.app.Applicationimport android.graphics.Bitmapimport androidx.compose.foundation.Imageimport androidx.compose.foundation.layout.Arrangementimport androidx.compose.foundation.layout.Boximport androidx.compose.foundation.layout.Columnimport androidx.compose.foundation.layout.Rowimport androidx.compose.foundation.layout.fillMaxSizeimport androidx.compose.foundation.layout.fillMaxWidthimport androidx.compose.foundation.layout.paddingimport androidx.compose.foundation.layout.sizeimport androidx.compose.foundation.lazy.LazyColumnimport androidx.compose.foundation.shape.CircleShapeimport androidx.compose.foundation.text.KeyboardActionsimport androidx.compose.foundation.text.KeyboardOptionsimport androidx.compose.material3.Buttonimport androidx.compose.material3.ButtonDefaultsimport androidx.compose.material3.CircularProgressIndicatorimport androidx.compose.material3.HorizontalDividerimport androidx.compose.material3.MaterialThemeimport androidx.compose.material3.OutlinedTextFieldimport androidx.compose.material3.Scaffoldimport androidx.compose.material3.Textimport androidx.compose.runtime.Composableimport androidx.compose.runtime.collectAsStateimport androidx.compose.ui.Alignmentimport androidx.compose.ui.Alignment.Companion.Centerimport androidx.compose.ui.Alignment.Companion.CenterHorizontallyimport androidx.compose.ui.Modifierimport androidx.compose.ui.draw.clipimport androidx.compose.ui.graphics.asImageBitmapimport androidx.compose.ui.layout.ContentScaleimport androidx.compose.ui.platform.LocalFocusManagerimport androidx.compose.ui.platform.LocalSoftwareKeyboardControllerimport androidx.compose.ui.text.font.FontWeightimport androidx.compose.ui.text.input.ImeActionimport androidx.compose.ui.text.input.KeyboardTypeimport androidx.compose.ui.text.style.TextAlignimport androidx.compose.ui.unit.dpimport androidx.lifecycle.viewmodel.compose.viewModelimport com.arcgismaps.toolkit.authentication.AuthenticatorStateimport com.arcgismaps.toolkit.authentication.DialogAuthenticatorimport com.esri.arcgismaps.sample.sampleslib.components.MessageDialogimport com.esri.arcgismaps.sample.sampleslib.components.SampleTopAppBarimport com.esri.arcgismaps.sample.showportaluserinfo.components.AppViewModel
/** * Main screen layout for the sample app */@Composablefun MainScreen(sampleName: String, application: Application) {
val appViewModel = viewModel { AppViewModel(application) } val authenticatorState: AuthenticatorState = appViewModel.authenticatorState
Scaffold( topBar = { SampleTopAppBar(title = sampleName) }, content = { Column( modifier = Modifier .fillMaxSize() .padding(it) ) { val infoText = appViewModel.infoText.collectAsState().value val isLoading = appViewModel.isLoading.collectAsState().value PortalDetails( url = appViewModel.url.collectAsState().value, onSetUrl = appViewModel::setUrl, onSignOut = appViewModel::signOut, onLoadPortal = appViewModel::loadPortal ) InfoScreen( infoText = infoText, username = appViewModel.portalUserName.collectAsState().value, email = appViewModel.emailID.collectAsState().value, creationDate = appViewModel.userCreationDate.collectAsState().value, portalName = appViewModel.portalName.collectAsState().value, userThumbnail = appViewModel.userThumbnail.collectAsState().value, isLoading = isLoading ) // display a dialog if the sample encounters an error appViewModel.messageDialogVM.apply { if (dialogStatus) { MessageDialog( title = messageTitle, description = messageDescription, onDismissRequest = ::dismissDialog ) } } } DialogAuthenticator(authenticatorState = authenticatorState) } )}
/** * Allows the user to enter a [url] and load a portal. * It uses OAuth under the hood, and has a button to clear credentials. * */@Composableprivate fun PortalDetails( url: String, onSetUrl: (String) -> Unit, onSignOut: () -> Unit, onLoadPortal: () -> Unit) { Column( modifier = Modifier .fillMaxWidth() .padding(8.dp), horizontalAlignment = CenterHorizontally, verticalArrangement = Arrangement.Center ) { // The Url text field OutlinedTextField( modifier = Modifier.fillMaxWidth(), value = url, onValueChange = onSetUrl, label = { Text("Portal URL") }, keyboardOptions = KeyboardOptions( keyboardType = KeyboardType.Uri, imeAction = ImeAction.Go ), keyboardActions = KeyboardActions(onAny = { onLoadPortal() }), singleLine = true ) Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { val keyboardController = LocalSoftwareKeyboardController.current val focusManager = LocalFocusManager.current // Clear credential button Button( onClick = { onSignOut() keyboardController?.hide() focusManager.clearFocus() }, colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.tertiary) ) { Text(text = "Sign out") } // Load button Button( onClick = { onLoadPortal() keyboardController?.hide() focusManager.clearFocus() }, colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.primary) ) { Text(text = "Load portal") } } }}
/** * Displays messages to the user. This may be used to display instructions, portal info, or error messages. * */@Composableprivate fun InfoScreen( infoText: String, username: String, email: String, creationDate: String, portalName: String, userThumbnail: Bitmap, isLoading: Boolean) { Box( Modifier .fillMaxSize() .padding(8.dp), ) { LazyColumn { item { Box(Modifier.fillMaxWidth()) { if (isLoading) CircularProgressIndicator( Modifier.align(Center).padding(10.dp) ) else Text( modifier = Modifier.align(Center).padding(10.dp), textAlign = TextAlign.Center, text = infoText ) } HorizontalDivider() Row( modifier = Modifier .padding(10.dp) .fillMaxWidth(), horizontalArrangement = Arrangement.Center ) { Image( bitmap = userThumbnail.asImageBitmap(), contentDescription = "User Thumbnail", contentScale = ContentScale.Crop, modifier = Modifier .clip(CircleShape) .size(150.dp) ) } HorizontalDivider() Row(modifier = Modifier.padding(10.dp)) { Text(text = "Username: ", fontWeight = FontWeight.Bold) Text(text = username) } HorizontalDivider() Row(modifier = Modifier.padding(10.dp)) { Text(text = "E-mail: ", fontWeight = FontWeight.Bold) Text(text = email) } HorizontalDivider() Row(modifier = Modifier.padding(10.dp)) { Text(text = "Member Since: ", fontWeight = FontWeight.Bold) Text(text = creationDate) } HorizontalDivider()
Row(modifier = Modifier.padding(10.dp)) { Text(text = "Portal Name: ", fontWeight = FontWeight.Bold) Text(text = portalName) } HorizontalDivider() } } }}