Disclaimer
I’ve been trying to learn to code android apps over the summer. I’ve been working through the Android Basics in Kotlin course, and I’ve recently hit the point where I felt I could branch off on my own a bit.
Context
I am trying to make a character sheet app for the recently released anime 5e d&d game. My goal is just to learn how to program apps. Presently I have a MainActivity class that manages navigation via a navdrawer, and that’s working fine. So I’ve started working on the first fragment (called AboutFragment) hosted by MainActivity. This is where the user will input things such as their name, their character’s name, etc.
Problem
At present, I am using exposed dropdown menus to allow the user to select their character’s race and subrace. The selected race should determine which subraces will be available for them to select. For instance, if the user selects the "Elf" race, they should only be able to pick between, "None", "Dark", "High", or "Wood" when choosing their subrace.
As seen in the code below, I can set up the race dropdown menu just fine. But the subrace menu is currently being set up before the user has a chance to actually select a race. So the subrace menu never changes from "Please select a race".
I am under the impression that I have to create the subrace dropdown menu in the onCreateView method, but that leaves no way for the user to determine their race before the subrace menu is created. I need to be able to change which array is displayed in the subrace menu after the user has selected a race.
Thanks in advance!!!
AboutFragment Code
package com.example.anime5echaractersheet
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import androidx.core.widget.doAfterTextChanged
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.activityViewModels
import com.example.anime5echaractersheet.databinding.FragmentAboutBinding
import com.example.anime5echaractersheet.model.SharedViewModel
/**
* A simple [Fragment] subclass.
* Use the [AboutFragment.newInstance] factory method to
* create an instance of this fragment.
*/
class AboutFragment : Fragment() {
private val sharedViewModel: SharedViewModel by activityViewModels()
private var _binding: FragmentAboutBinding? = null
private val binding get() = _binding!!
/*
* initialize binding, set up race selection dropdown menu, inflate layout
* */
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentAboutBinding.inflate(inflater, container, false)
binding.lifecycleOwner = viewLifecycleOwner
//setup race dropdown
val races = resources.getStringArray(R.array.races)
val raceAdapter = ArrayAdapter(requireActivity(), R.layout.dropdown_item, races)
binding.raceAutocompleteTextView.setAdapter(raceAdapter)
//setup subrace dropdown
val subraces: Array<String> = createSubraceMenu()
val subraceAdapter = ArrayAdapter(requireActivity(), R.layout.dropdown_item, subraces)
binding.subraceAutocompleteTextView.setAdapter(subraceAdapter)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.apply { viewModel = sharedViewModel }
//update viewModel race & size when user makes changes
binding.raceAutocompleteTextView.doAfterTextChanged {
binding.viewModel?.updateRaceAndSize(binding.raceAutocompleteTextView.text.toString())
}
}
/*
* nullify _binding before destroying fragment
* */
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
private fun createSubraceMenu(): Array<String> {
return when (binding.viewModel?.race?.value) {
"Archfiend" -> resources.getStringArray(R.array.archfiend_subraces)
"Asrai" -> resources.getStringArray(R.array.asrai_subraces)
"Blinkbeast" -> resources.getStringArray(R.array.blinkbeast_subraces)
"Crogoblin" -> resources.getStringArray(R.array.crogoblin_subraces)
"Demonaga" -> resources.getStringArray(R.array.demonaga_subraces)
"Dragonborn" -> resources.getStringArray(R.array.dragonborn_subraces)
"Dwarf" -> resources.getStringArray(R.array.dwarf_subraces)
"Elf" -> resources.getStringArray(R.array.elf_subraces)
"Fairy" -> resources.getStringArray(R.array.fairy_subraces)
"Gnome" -> resources.getStringArray(R.array.gnome_subraces)
"Grey" -> resources.getStringArray(R.array.grey_subraces)
"Half-Dragon" -> resources.getStringArray(R.array.half_dragon_subraces)
"Half-Elf" -> resources.getStringArray(R.array.half_elf_subraces)
"Half-Orc" -> resources.getStringArray(R.array.half_orc_subraces)
"Half-Troll" -> resources.getStringArray(R.array.half_troll_subraces)
"Halfling" -> resources.getStringArray(R.array.halfling_subraces)
"Haud" -> resources.getStringArray(R.array.haud_subraces)
"Human" -> resources.getStringArray(R.array.human_subraces)
"Kodama" -> resources.getStringArray(R.array.kodama_subraces)
"Loralai" -> resources.getStringArray(R.array.loralai_subraces)
"Nekojin" -> resources.getStringArray(R.array.nekojin_subraces)
"Parasite" -> resources.getStringArray(R.array.races)
"Raceless" -> resources.getStringArray(R.array.raceless_subraces)
"Satyr" -> resources.getStringArray(R.array.satyr_subraces)
"Slime" -> resources.getStringArray(R.array.slime_subraces)
"Tiefling" -> resources.getStringArray(R.array.tiefling_subraces)
else -> arrayOf("Please select a race")
}
}
}
fragment_about layout
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="viewModel"
type="com.example.anime5echaractersheet.model.SharedViewModel" />
</data>
<ScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
tools:context=".AboutFragment">
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.Anime5eCharacterSheet.TextInputLayout.FilledBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:helperText="@string/player_name_helper_text">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/player_name_edit_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:autoSizeTextType="uniform"
android:hint="@string/player_name"
android:inputType="textCapWords" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.Anime5eCharacterSheet.TextInputLayout.FilledBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:helperText="@string/character_name_helper_text">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/character_name_edit_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:autoSizeTextType="uniform"
android:hint="@string/character_name"
android:inputType="textCapWords" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.MaterialComponents.TextInputLayout.FilledBox.ExposedDropdownMenu"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/text_input_layout_margin"
app:helperText="@string/race_helper_text">
<com.google.android.material.textfield.MaterialAutoCompleteTextView
android:id="@+id/race_autocomplete_text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/race"
android:inputType="none" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.MaterialComponents.TextInputLayout.FilledBox.ExposedDropdownMenu"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/text_input_layout_margin"
app:helperText="Select a subrace from the dropdown menu">
<com.google.android.material.textfield.MaterialAutoCompleteTextView
android:id="@+id/subrace_autocomplete_text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/subrace"
android:inputType="none" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.Anime5eCharacterSheet.TextInputLayout.FilledBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:helperText="@string/size_helper_text">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/size"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:focusable="false"
android:hint="@string/size"
android:inputType="none"
android:longClickable="false"
android:text="@{viewModel.size}"
android:textIsSelectable="true" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.Anime5eCharacterSheet.TextInputLayout.FilledBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:helperText="@string/description_helper_text">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:autoSizeTextType="uniform"
android:hint="@string/description"
android:inputType="textMultiLine" />
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
</ScrollView>
</layout>
string.xml
<resources>
<string name="app_name">Anime 5e Character Sheet</string>
<!--fragment labels-->
<string name="about">About</string>
<string name="stats">Stats</string>
<string name="start">Start</string>
<!--textfield labels-->
<string name="player_name">Player Name</string>
<string name="character_name">Character Name</string>
<string name="race">Race</string>
<string name="description">Description</string>
<string name="size">Size (READ ONLY)</string>
<string name="subrace">Subrace</string>
<!--textfield helpertext-->
<string name="player_name_helper_text">Enter your name</string>
<string name="character_name_helper_text">Enter your character's name</string>
<string name="race_helper_text">Select a race from the dropdown menu</string>
<string name="size_helper_text">Your character's size (READ ONLY)</string>
<string name="description_helper_text">Enter a physical description of your character</string>
<!--races for race dropdown-->
<string-array name="races">
<item>Archfiend</item>
<item>Asrai</item>
<item>Blinkbeast</item>
<item>Crogoblin</item>
<item>Demonaga</item>
<item>Dragonborn</item>
<item>Dwarf</item>
<item>Elf</item>
<item>Fairy</item>
<item>Gnome</item>
<item>Grey</item>
<item>Half-Dragon</item>
<item>Half-Elf</item>
<item>Half-Orc</item>
<item>Half-Troll</item>
<item>Halfling</item>
<item>Haud</item>
<item>Human</item>
<item>Kodama</item>
<item>Loralai</item>
<item>Nekojin</item>
<item>Parasite</item>
<item>Satyr</item>
<item>Slime</item>
<item>Tiefling</item>
</string-array>
<!--subraces for subrace dropdown-->
<string-array name="archfiend_subraces">
<item>Aerial</item>
<item>None</item>
</string-array>
<string-array name="asrai_subraces">
<item>Blessed</item>
<item>None</item>
</string-array>
<string-array name="blinkbeast_subraces">
<item>Multi</item>
<item>None</item>
</string-array>
<string-array name="crogoblin_subraces">
<item>None</item>
<item>Steel</item>
</string-array>
<string-array name="demonaga_subraces">
<item>Aqua</item>
<item>Fire</item>
</string-array>
<string-array name="dragonborn_subraces">
<item>Black</item>
<item>Blue</item>
<item>Brass</item>
<item>Bronze</item>
<item>Copper</item>
<item>Gold</item>
<item>Green</item>
<item>Red</item>
<item>Silver</item>
<item>White</item>
</string-array>
<string-array name="dwarf_subraces">
<item>Hill</item>
<item>Mountain</item>
</string-array>
<string-array name="elf_subraces">
<item>None</item>
<item>Dark</item>
<item>High</item>
<item>Wood</item>
</string-array>
<string-array name="fairy_subraces">
<item>Rock</item>
<item>Woodland</item>
</string-array>
<string-array name="gnome_subraces">
<item>None</item>
<item>Forest</item>
<item>Rock</item>
</string-array>
<string-array name="grey_subraces">
<item>Elite</item>
<item>None</item>
</string-array>
<string-array name="half_dragon_subraces">
<item>Bronze</item>
<item>Copper</item>
<item>Gold</item>
<item>Silver</item>
</string-array>
<string-array name="half_elf_subraces">
<item>None</item>
</string-array>
<string-array name="half_orc_subraces">
<item>None</item>
</string-array>
<string-array name="half_troll_subraces">
<item>None</item>
<item>Water</item>
</string-array>
<string-array name="halfling_subraces">
<item>None</item>
<item>Lightfoot</item>
<item>Stout</item>
</string-array>
<string-array name="haud_subraces">
<item>Leaping</item>
<item>None</item>
</string-array>
<string-array name="human_subraces">
<item>Standard</item>
<item>Variant</item>
</string-array>
<string-array name="kodama_subraces">
<item>None</item>
<item>Void</item>
</string-array>
<string-array name="loralai_subraces">
<item>None</item>
<item>Paired</item>
</string-array>
<string-array name="nekojin_subraces">
<item>None</item>
<item>Panthera</item>
</string-array>
<string-array name="raceless_subraces">
<item>None</item>
</string-array>
<string-array name="satyr_subraces">
<item>Forest</item>
<item>Mountain</item>
<item>Under</item>
</string-array>
<string-array name="slime_subraces">
<item>Blue</item>
<item>Other</item>
</string-array>
<string-array name="tiefling_subraces">
<item>Bloodline of Asmodeus</item>
<item>None</item>
</string-array>
</resources>
2
Answers
Update
A custom array adapter was, in fact, the correct thing to implement. I simply created a new adapter called SubraceAdapter which could take in in a reference to the 'race' LiveData from my sharedViewModel. I created a method inside of the SubraceAdapter that would clear the dropdown menu, check the LiveData, and then add in the correct subrace options. This method is called in onViewCreated each time raceAutoCompleteTextView.doAfterTextChanged is triggered. My solution code is below:
AboutFragment
SubraceAdapter
craete a custom array adapter for autoCompleteTextView
}