How would I mimic the design of a preference screen or the "edit Contact" screen of android, without doing too much work?
I have to display ~12 attributes for an object and can't get my head around how to display them in a good way.
not for every attribute an obvious icon exists -> we want labels?
displaying textviews underneath other textviews that have an icon looks horrible, except if you want to manually add margins. (indenting)
PreferenceFragment has a kind of "Title" that can be used as Label and a Value, which would be even more perfect, as I have some interactive elements (item picker).
But using preferencefragment without SharedPreferences and instead to display arbitrary objects is obviously a (bad?) hack.
I've searched a lot and didn't come to a conclusion. Is there an obvious way I missed ?
You can actually create Settings Activity (with full functionality) without much effort.
Go to File > New > Activity > Settings Activity.
It will create the entire functionality - you only need to specify Switches, Lists in
res > xml > prefs_.xml
The values that you set via screen will be saved in SharedPreferences under the specified key.
To intercept the values that are configured via UI to the SharedPreferences you can add this to the SettingsActivity:
class SettingsFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.pref_general, rootKey)
findPreference<ListPreference>("system_theme")?.setOnPreferenceClickListener {
val value = (it as? ListPreference)?.value
//send value to server
return#setOnPreferenceClickListener true
}
}
}
Related
There are some controls in our app which we'd like to update the control type read out by Talkback. For example: A TextView which would better be described as a link, or an ImageView that would better be described as a button.
We could simply update the content description to report out the desired control type, though I am wondering if there is a better way? If there is another way, can it be done both through the view XML and dynamically in the code behind?
Yes, it is possible to change the type. It is called roleDescription. You would change it as follows:
ViewCompat.setAccessibilityDelegate(yourView,
object : AccessibilityDelegateCompat() {
override fun onInitializeAccessibilityNodeInfo(v: View, info: AccessibilityNodeInfoCompat) {
super.onInitializeAccessibilityNodeInfo(v, info)
info.roleDescription = "Button"
}
})
(use string resources and translate the strings to all languages supported by your app)
This cannot be done via XML by default, but you could look into writing your own binding adapter for this.
Hello StackOverflow users!
I'm trying to do someting complicated (for a noob like me XD) and I have some questions.
How can I set my main screen buttons position form a preference screen? Like, if I have 2 buttons to inverse their position...
I tried to do with the code below in kotlin (please don't judge me, already told u I'm noob af) but I wanna have an idea how to make this wotk. XD
findPreference<Preference>("buttonpositions")!!.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { preference: Preference, _: Any? ->
val checked = (preference as CheckBoxPreference).isChecked
if(!checked) {
val myBtn = R.id.cleanBtn as Button
myBtn.setPadding(0, 100, 0, 0)
}
true
}
and the buttons is something like this:
<com.google.android.material.button.MaterialButton
style="#style/Widget.Material3.Button.UnelevatedButton"
android:id="#+id/cleanBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_columnWeight="1"
android:minWidth="0dp"
app:layout_rowWeight="1"
android:tooltipText="#string/tooltip_clean_button"
app:iconGravity="top"
app:icon="#drawable/ic_broom"
app:iconTint="#android:color/system_accent1_400"
android:textColor="#android:color/system_accent1_400"
android:text="#string/clean"/>
<com.google.android.material.button.MaterialButton
style="#style/Widget.Material3.Button.UnelevatedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_columnWeight="1"
app:layout_rowWeight="1"
android:id="#+id/analyzeBtn"
app:iconGravity="top"
app:icon="#drawable/ic_search"
android:tooltipText="#string/analyze_tooltip"
app:iconTint="#android:color/system_accent1_700"
android:textColor="#android:color/system_accent1_700"
android:text="#string/analyze"
android:minWidth="0dp"
app:backgroundTint="#android:color/transparent"/>
First. I'm preety sure my "code" isn't right composed + idk if I need to add someting to buttons itself.
Thanks for your time :D
Why your code won't work:
R.id.cleanBtn is an Int ID, not a button. Trying to cast an Int into a Button will crash the app. (Casting is not the same thing as converting--it's promising the compiler that something is also something else already.) IDs are used to search existing view hierarchies for the actual view you want by using something like rootView.findViewById<Button>(R.id.cleanBtn), but this strategy isn't practical here since you don't have direct access to the view hierarchy.
Trying to change the button from the preference listener would only work when you change it. If the user rotates the screen or the exits the app and comes back, the main screen's view will be recreated back in the original position.
The right way to do this is not on the settings screen, but on the main screen. Preferences that are set on a preference screen by default are saved to the "default" shared preferences, and they are saved persistently, so they can be read from anywhere else in your app.
What I would do is read the current value of the preference in the onResume() function of the activity or fragment of your main screen. onResume() will get called every time that screen reappears, so it gives it an opportunity to check and apply the setting the first time the view appears and every time you return from the preferences screen, where the value may have changed.
//in main screen class
private var isButtonPositionFlipped = false
// Only need this function if in a Fragment
// Need to reset the property in case Fragment has recreated its view
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//...
isButtonPositionFlipped = false
}
override fun onResume() {
super.onResume()
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
// use requireContext() instead of this if we're in a Fragment class
val shouldFlipButtonPosition = sharedPreferences.getBoolean("buttonpositions", false)
if (isButtonPositionFlipped != shouldFlipButtonPosition) {
isButtonPositionFlipped = shouldFlipButtonPosition
val cleanBtn = findViewById<Button>(R.id.cleanBtn)
val analyzeBtn = findViewById<Button>(R.id.analyzeBtn)
// use requireView().findViewByID if in a Fragment class
if (isButtonPositionFlipped) {
// swap the buttons here
} else {
// swap them back here
}
}
}
As for how to swap them, that depends on the type of layout they're in. If they're in a ConstraintLayout, I think it will be pretty complicated, but I haven't tried it before. You might try putting each of the two buttons inside FrameLayouts. Then you could get the FrameLayout views and swap the view children between them.
Edit: Actually, I think it would be easier in this case to just make your buttons more generically named. Instead of swapping them, just change their text and click listeners to the opposites.
I have successfully completed a navigation into RecyclerViewAdapter to navigate many destinations by string Resource. Because I have many lists, and each TextView it's about separate fragment.
It's wonderful to do it. But I have a small problem. That's I have 2 string Resource, "en" English as a default and "ar" as a second language.
My app is working well when I use it by English locale. But it crashes when I use it by Arabic locale.
What I want is:
To control or force the app when it converts to Arabic to still use the default, which is English string resource.
Here's the RecyclerViewAdapter code block:
override fun onBindViewHolder(holder: SubSectionListHolder, position: Int) {
val item = dataset[position]
holder.subSectionView.text = context.resources.getString(item.subSectionSrcID)
holder.subSectionView.setOnClickListener {
val stringConvertToId = it.resources.getIdentifier(
context.resources.getString(item.subSectionSrcID).replace("\\s".toRegex(), ""),
"id",
context.packageName)
it.findNavController().navigate(stringConvertToId)
}
}
Here's sample of the navGraph tag:
<fragment
android:id="#+id/CreateOrder"
android:name=".PoCreateOrderFragment"
android:label="#string/btnStr_crtOrder"
tools:layout="#layout/fragment_po_create_order">
</fragment>
<fragment
android:id="#+id/ReceivedPreOrders"
android:name=".PoRcOrdersFragment"
android:label="#string/str_whPo_rcvdPrm"
tools:layout="#layout/fragment_po_rc_orders">
</fragment>
<fragment
android:id="#+id/DeferredPreOrders"
android:name=".PoDfOrdersFragment"
android:label="#string/str_whPo_dfrdPrm"
tools:layout="#layout/fragment_po_df_orders">
</fragment>
Here's sample of the default string Resource tag:
<string name="btnStr_crtOrder">Create Order</string>
<string name="str_whPo_rcvdPrm">Received PreOrders</string>
<string name="str_whPo_dfrdPrm">Deferred PreOrders</string>
Here's sample of the Arabic string Resource tag:
<string name="btnStr_crtOrder">إضافة طلب</string>
<string name="str_whPo_rcvdPrm">الطلبيات المستلمة</string>
<string name="str_whPo_dfrdPrm">الطلبيات المؤجلة</string>
To completely reach my idea to you.
This is really brittle (as you're finding out!) and you're just going to create headaches for yourself with this kind of complicated stuff. You should really keep the UI (e.g. the text being displayed) completely separate from the business logic (in this case, uniquely identifying each item and doing a specific action based on which one is clicked). The way you're doing it, it completely breaks whenever the display text is changed
You're already holding a list of items with a resource string ID, right? And looking them up by index using the RecyclerView position. If I were you, I'd just create a lookup associating each item with a hardcoded navigation resource ID.
You could make another list with all the navigation IDs and use position to grab the correct one. Or you could make a Map associating each string resource ID with its navigation ID:
val labelsToDestinations = mapOf(
R.string.btnStr_crtOrder to R.id.createOrder,
...
)
// in onBindViewHolder
holder.subSectionView.setOnClickListener {
val destination = labelsToDestinations[item.subSectionSrcID]
it.findNavController.navigate(destination)
}
That way, it doesn't matter what the value of the string resource is, you're just looking it up by the resource's ID. The value can change (different languages, different wording) and that doesn't matter.
Or just make it another property on the item (e.g. item.destinationId), like your label string ID already is. Personally, if I have a fixed set of things I need to define like this, I usually make an enum (you could use a sealed class if you want:
enum class DestinationItem(#StringRes labelId: Int, #IdRes navigationId: Int) {
CREATE_ORDER(R.string.btnStr_crtOrder, R.id.createOrder)
RECEIVED_PRE_ORDERS(R.string.str_whPo_rcvdPrm, R.id.receivedPreOrders)
...
}
val items = DestinationItem.values()
then you can generate your list of items from that, and you have access to all the important IDs on the item itself. You can easily change which resources they use without affecting anything else - you can use a different label resource to control the display, that won't affect the navigation ID because it's a completely separate property
as I wrote in the title, the Android Talkback actually reads the hint text of my search bars, but I need to change that behavior to read a custom string (different from the hint). I tried to set the contentDescription but it didn't work (the talkback still reads the hint text).
Do you have any advice?
You can set an AccessibilityDelegate on a view to override things like how it describes itself in general:
ViewCompat.setAccessibilityDelegate(yourView, object : AccessibilityDelegateCompat(){
override fun onInitializeAccessibilityNodeInfo(host: View?, info: AccessibilityNodeInfoCompat?) {
super.onInitializeAccessibilityNodeInfo(host, info)
info?.let { it.text = "whatever" }
}
})
or you can override onPopulateAccessibilityEvent (which handles triggered events like AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED) and use event.getText(), which gives you a List<CharSequence> you can add your description to. It really depends on what you're doing and how dynamic it needs to be.
I haven't found a good overview of all this stuff unfortunately, otherwise I'd link you to it. I think the delegates are what you need to look into though
I'm creating an app where I need to quickly test some different languages. I've got 3 string resource files, values/strings.xml, values-es/strings.xml, and values-fr/strings.xml. Each of the files have their respective translated strings.
When I launch the app, I'm preloading some text into a TextView (Locale.getDefault().displayLanguage) and text that I pull in from an array in the xml file and assign one string to an EditText:
<string-array name="some_text">
<item>Overall, how severe were your flu symptoms today? Please select one response only.</item>
<item>No flu symptoms today</item>
<item>Mild</item>
<item>Moderate</item>
<item>Severe</item>
<item>Very Severe</item>
</string-array>
I set the text of the language label like this:
languageTxt = findViewById(R.id .language_text)
languageTxt.text = Locale.getDefault().displayLanguage
Then load the strings in the array and set one of them to the EditText like this:
textList = resources.getStringArray(R.array.some_text)
editTxt = findViewById(R.id.edit_text)
if(textList.isNotEmpty()) {
val txt = textList[currentIndex] //currentIndex = 0
editTxt.setText(txt)
}
textList, languageTxt, and editTxt are declared private like:
private lateinit var textList: Array<String>
private lateinit var languageTxt: TextView
private lateinit var editTxt: EditText
The first run through onCreate, everything works fine. If the language is set to Spanish, both languageTxt and editTxt are in Spanish as expected. Then if I go to settings and change the language to French, when I bring my app back to the foreground, the languageTxt says French (well spelled in French). Then I watch in the debugger, the textList loading the text, and it's all the French strings like I expected. The problem I'm seeing is when I set editText, the control isn't updating to display the French string. It's still in Spanish. I can clearly see that the text is in French, but calling setText() doesn't seem to be working. I've tried clearing the exitTxt first, setting it to null, empty, etc, nothing seems to work. Does anyone know what's going on here? I've been scratching my head for a couple hours. Ugh
Thanks to Giddy Naya's answer for giving me a hint on what is going on. Android saves the state of the app when it goes in the background so that it's the same when it comes back to the foreground. It's so strange, because even a editTxt.setText() doesn't work and gets overridden by Android. So what I had to do is set the new state of app (i.e. update the text) in the onResume function:
override fun onResume() {
super.onResume()
if(textList.isNotEmpty()) {
val txt = textList[currentIndex]
editTxt.setText(txt)
}
}
If you're new to Android, this feature can make debugging quite a pain, since you're trying to set a new state in onCreate and it doesn't change.
I'm still not sure why setting a TextView works in onCreate() but setting an EditText does not.
If your app is in foreground during language changes your UI may need reloading onResume so expected resources can be loaded.
You can save the default language in preference and check for value changes onActivityResume and if true then reload UI.
Something like
override fun onResume() {
super.onResume()
...
if (localeHasChanged)
setContentView(R.layout.xxx) //force UI reload
...
}