Handle display of string or string resource - android

Suppose you apply the following architecture in your Android project:
View - ViewModel - UseCase - Repository and you have to display some text from the UseCase layer to the View layer. However, this text some times is of type String and some times is of type Int (stringResource).
What is the best practice to handle this?

Maybe you can try type Any and set case to decalre type as String || as Int

Related

Android: How to change the control type for Accessibility

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.

How to control navigation by default string resource in RecyclerView?

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

MenuItem#getTag()

On subclasses of View there is a getTag() method, which returns the android:tag attribute's value from .xml.
I would like the same for a MenuItem... is it okay to just cast it to a View?
Because item elements also allow a tag attribute in .xml...
Update: My goal with this is setting a tag in .xml, i.e. "notranslate", and querying it at runtime (we localize by hand at runtime, don't ask...)
It is always alright to cast, however, casting any Interface cannot be checked at compile time, only runtime. This is normally the reason many do not recommend casting an Interface that you have no control over. Having the proper error checking code is the best way to insure that such a cast does not break your code.
For the casting, it doesn't really matter whether the MenuItem is an Interface or a View, but the object it references must be one of View's subclasses, if not a View itself. If you are going to cast it, try the cast and catch a ClassCastException just in case as this is the error that will be thrown in runtime.
Another option is that since the MenuItem is simply an interface, you can easily just create a View subclass that utilizes MenuItem allowing you to do the cast. If you are doing a custom ContextMenu as many launchers do, then chances are your answer is nearly complete.
Hope this helps,
FuzzicalLogic
MenuItem is an interface. Any class can implement this interface and so it will not always be safe to cast the MenuItem to a View. You can use the "instanceOf" operator to test to see if the object that implements the MenuItem interface is indeed a View or not.
I understand that you want to define a flag in the XML definition of the menu and then at run time interrogate that flag to make a programmatic decision.
The Menu Resource Documentation records what attributes can be set in the XML. You can consider using (abusing) one of those settings such as the "android:alphabeticShortcut" to encode the flag and use the MenuItem::getAlphabeticShortcut() method to get the value. This does not require casting - it just uses the existing fields in the MenuItem XML construct/class for your own purposes.
Perhaps a less hacky way to do this is to keep a simple table in a separate assets file that lists the menu item identifiers and the special behavior associated with that identifier such as to translate or not to translate.
Alternatively create a simple class that has a table with this configuration information hard coded using the logical "#[+][package:]id/resource_name" resource identifier as the keys to the table. While this doesn't keep it all in one place (in the XML) it does it in a manner that is not encoding information in unused attributes, or relying on the ids not changing. The "table" could be implemented as a static method with an embedded switch statement allowing code such as "if (TranslationTable.shouldTranslate(menuItem.getItemId())) { do translation }"
I had a similar problem in that I wanted to associate some arbitrary data with each menu item so that I could handle menu items in a generic way without having to use hardcoded checks for individual item ids in code.
What I did was for a particular menu item (e.g. #+id/foo) There was an a TypedArray that was defined using the same name as the menu item ID. You could do this with other types of resources as well.
So to do the association, you get the resouce entry name (foo in my example) and then use that to look up the id of the other resource of a different type (#array/foo in my example).
In my handler for menu I had code like this:
Resources resources = getResources();
String name = resources.getResourceEntryName(item.getItemId());
int id = resources.getIdentifier(name, "array", "com.example");
if(id != 0)
{
TypedArray data = resources.obtainTypedArray(id);
// Use the typed array to get associated data
}
EDIT:
Actually it is even easier than that. There is nothing special about the ids on menu items other than you don't want multiple menu items with the same id. The id does not have to be of the form #+id/foo. It can actually also refer to other resources. So in my example above, instead of having the menu have an id of #+id/foo and using the resource manager to use that to find #array/foo, I changed to actually have the menu item have the id of #array/foo.
Now in my onOptionsItemSelected I have this:
Resources resources = getResources();
if("array".equals(resources.getResourceTypeName(item.getItemId())))
{
TypedArray data = resources.obtainTypedArray(item.getItemId());
// Use the typed array
}

can BitmapFactory.decodeResource(context,int) take context string

I have a method that returns a String(let's say the var sprite) and this string is the name of my bitmap img,and i want to use it with BitmapFactory.decodeResource() but i don't know how to combine this since it must be int R.drawable.sprite
Can this be done?
Yes, it's possible. Take a look at this question:
Android and getting a view with id cast as a string

Android and storing/loading preferences for resources - how to achieve consistency?

I'm writing an application and need some help with consistently storing and loading preferences related to certain resources. Let me give an example.
Suppose I have 10 spinners. They all have the same functionality but different meaning. Because they have the same functionality, I can bind them to the same onItemSelectedListener, which will then deal with the selected value.
Well, what if I also want to store the preferences for each spinner? For example, when a spinner value is selected, I'd store a key "spinner SOMETHING" = SOME_DATA.
The problem is - what should this SOMETHING be? The listener has the following signature:
public void onItemSelected(AdapterView parent, View v, int position, long id)
The position and id are not helpful here as they're set relative to each spinner.
However, the parent, which in this case is the spinner itself, should have all the information I need. I tried using parent.getId() to get a resource ID of each spinner and store the preference using a key "spinner ID" but apparently these resource IDs change when you add more resources into your app, so my settings would get reset because the keys change.
What should I do here? I can't seem to find a simple parent.getName() function of some sort that would return me a consistent name of each spinner.
Thank you.
The getId is the only thing I know of that has a unique reference to the View, but as you say, a view might not have the same Id in the next build of your app.
The best human-readable identifier available out of the box would be getPrompt, which would simply show you whatever you have set as the spinners prompt. It should be an OK identifier, but it is of course also prone to breakage if you manually change your prompt texts.
The most break-proof way, then, is to specify an identifier in the general container - tag - that every View contains.
String myIdentifier = (String) parent.getTag();
If you're already using the tag for something else, the 1.6+ APIs include the ability to specify several tags, and access them via index. If you need to support older API versions, you could always set the tag to a wrapper object that contains both the identifier, and your original tag.

Categories

Resources