Create your Password Generator in Jetpack Compose | Part 3 | Password Edit UI

WhiteBatCodes
5 min readMay 29, 2023

--

Password generators are commonly used to generate strong passwords for your accounts. This application is for you to make your own custom Password generator app and even store those passwords in a secure location for later usage.

In Part 2, we have configured the logic to generate the passwords. In this part, we’ll configure a UI to allow the user to save additional data for the password.

Let’s start with the strings first to save time. In your values/strings.xml file add the following

<string name="create_new_password">Create New password</string>
<string name="name">Name*</string>
<string name="login">Login*</string>
<string name="site_link">Website Link</string>
<string name="description">Description</string>
<string name="save">Save</string>

Then, create an empty activity : PasswordEditorActivity and add the content and the starter composable with a preview like so :

class PasswordEditorActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

setContent {
PasswordEditor()
}
}
}


@Composable
fun PasswordEditor() {
Text("ok")
}

@Preview(device = "id:Nexus One", showSystemUi = true)
@Composable
fun PasswordEditorPreview() {
PasswordVaultTheme {
PasswordEditor()
}
}

Once the preview is good, let’s start with the title of the page. So in the method PasswordEditor(), add the following :


@Composable
fun PasswordEditor() {
Surface {
Column(
modifier = Modifier
.fillMaxSize()
.padding(5.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(
stringResource(id = R.string.create_new_password),
modifier = Modifier.align(Alignment.CenterHorizontally),
style = MaterialTheme.typography.headlineSmall
)
}
}
}

I have prepared a set of text fields that we will be using for this UI. Create a file Composables.kt inside the package ui.helpers : with the following content :

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun PwdField(
value: String,
onChange: (String) -> Unit,
modifier: Modifier = Modifier,
keyboardActions: KeyboardActions = KeyboardActions.Default,
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
label: String = "Password",
placeholder: String = "Enter your Password"
) {

var isPasswordVisible by remember { mutableStateOf(false) }

val trailingIcon = @Composable {
IconButton(onClick = { isPasswordVisible = !isPasswordVisible }) {
Icon(
if (isPasswordVisible) Icons.Default.VisibilityOff else Icons.Default.Visibility,
contentDescription = "",
tint = MaterialTheme.colorScheme.primary
)
}
}


TextField(
value = value,
onValueChange = onChange,
modifier = modifier,
trailingIcon = trailingIcon,
keyboardOptions = keyboardOptions,
keyboardActions = keyboardActions,
placeholder = { Text(placeholder) },
label = { Text(label) },
singleLine = true,
visualTransformation = if (isPasswordVisible) VisualTransformation.None else PasswordVisualTransformation(),

)
}


@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun TxtField(
value: String,
onChange: (String) -> Unit,
modifier: Modifier = Modifier,
label: String = "Login",
placeholder: String = label,
singleLine: Boolean = true,
leadingIcon: @Composable (() -> Unit)? = null,
keyboardActions: KeyboardActions = KeyboardActions.Default,
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
maxLines: Int = Int.MAX_VALUE
) {

TextField(
value = value,
onValueChange = onChange,
modifier = modifier
.fillMaxWidth(),
leadingIcon = leadingIcon,
keyboardOptions = keyboardOptions,
keyboardActions = keyboardActions,
placeholder = { Text(placeholder) },
label = { Text(label) },
singleLine = singleLine,
visualTransformation = VisualTransformation.None,
maxLines = maxLines
)
}

This file will need some extra icons for the password. Add the following implementation in your build.gradle, sync and import them :

implementation "androidx.compose.material:material-icons-extended:1.4.3"

Once we have our helpers, let’s create a password modal that will hold the password to be saved. The existing code already had a package called “modals.password” Let’s refactor this package to : “modals.passwordGen” and create a package modals.password and inside, add the data class SavedPassword.kt:

data class SavedPassword(
var title: String = "",
var id: Int = 0,
var login: String = "",
var password: String = "",
var siteUrl: String = "",
var description: String = "",
var created: Long = Date().time,
var updated: Long = Date().time
) {
fun mandatoryFieldsExist(): Boolean {
return title.isNotEmpty() && login.isNotEmpty() && password.isNotEmpty()
}
}

Back to the PasswordEditorActivity let’s add an instance of that class that we’ll pass as a parameter to the text fields we’ll create right after. So in the first line inside the PasswordEditor() method, add the following :

   var savedPassword by remember {
mutableStateOf(SavedPassword())
}

then right after the Text composable, add the following :

Spacer(Modifier.height(10.dp))
TxtField(
value = savedPassword.title,
onChange = {
savedPassword = savedPassword.copy(title = it)
},
modifier = Modifier
.padding(vertical= 5.dp),
label = stringResource(id = R.string.name)
)
TxtField(
value = savedPassword.login,
onChange = {
savedPassword = savedPassword.copy(login = it)
},
modifier = Modifier
.padding(vertical= 5.dp),
label = stringResource(id = R.string.login)
)
PwdField(
value = savedPassword.password,
onChange = {
savedPassword = savedPassword.copy(password = it)
},
modifier = Modifier
.fillMaxWidth()
.padding(vertical= 5.dp)
)
TxtField(
value = savedPassword.siteUrl,
onChange = {
savedPassword = savedPassword.copy(siteUrl = it)
},
modifier = Modifier
.padding(vertical= 5.dp),
label = stringResource(id = R.string.site_link)
)
TxtField(
value = savedPassword.description,
onChange = {
savedPassword = savedPassword.copy(description = it)
},
modifier = Modifier
.padding(vertical= 5.dp)
.heightIn(min = 80.dp),
label = stringResource(id = R.string.description)
)

Now, let’s add the save button and add a context to close the current activity to go back to the launcher activity. Add the context after the savedPassword:

val context = LocalContext.current

Under that description TxtField, add the following :

Button(
modifier = Modifier.fillMaxWidth(),
onClick = {
// send the data
(context as Activity).finish()
},
enabled = savedPassword.mandatoryFieldsExist()
) {
Text(stringResource(id = R.string.save))
}

}

Before we’ll close the activity, we’ll have to save the data, and this will be done in the next part.

So far, we have configured the interface of the Edit and what happens when the user clicks on Save to go back to the previous activity.

Now, let’s go back to our Password Generator activity and add the button save that will open the activity above. Inside the function PasswordGen() and after the “generate” button, add the following :

Spacer(modifier = Modifier.height(10.dp))
Button(
shape = RoundedCornerShape(5.dp),
onClick = {
context.startActivity(Intent(context,PasswordEditorActivity::class.java))
},
modifier = Modifier.fillMaxWidth(),
enabled = generatedPassword.isNotEmpty()
) {
Text(text = stringResource(id = R.string.save))
}

If you run your activity, you’ll notice that the password field is not populated. That’s because we’re not passing it to the next activity. After you fill the mandatory attributes and click on save, you’ll be brought back to the starter activity, which is the password generator activity.

You can add a preview to see how everything looks like or run your application to see the result.

The GitHub code can be found here : GitHub repo

Here’s a YouTube video if you want to follow the same steps more visually :

You can read about part 2 here : Create your Password Generator in Jetpack Compose | Part 2 | PwdGen

--

--

WhiteBatCodes

A software engineer👨‍💻 with a passion for IT 📱💻 and cyber security 🔐.