Theoretically implementation of multi culture stored in remote DB.
In Android we have the following:
<TextView
android:id="#+id/loginlabel"
android:layout_gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="25sp"
android:textStyle="bold"
android:layout_marginTop="5dp"
android:text="#string/login" />
And the text is using the value of the property login from the file string.xml.
Now my question: in the activity xml in the property android:text is there any way for me to call a function/method (can it even be from a static class) ?
A place that have some business logic and still returns a string.
I know that I can easily use the following type of code in my activity java file:
TextView loginlabel = (TextView) findViewById(R.id.loginlabel);
loginlabel.setText( GetMyText(R.id.loginlabel) );
But I am trying to avoid to write this a "million times" since I have several activities and hundreds of values on my strings.xml.
For adition information, my purpose with this is the following:
On application start I read all the string.xml and connect to my remote server (which has a DB) and verify if all the strings are inserted in that DB.
Insert the ones that are missing, with the default value of that property in string.xml.
Now, in another remote system, the client can dynamically create new cultures and translations of this properties.
back to the android, I load a list of the current cultures that we have available and also load the values (translations) from the DB. The only thing missing it to "inject" them into the place where I am using the "string" value.
This worked for me in the past.
public class MyTextView extends TextView {
public MyTextView(Context context) {
super(context);
}
public MyTextView(Context context, AttributeSet attrs) {
super(context, attrs);
setText(yourMethodWithBusinessLogic());
}
}
Don't forget to replace your normal textviews with this custom one. Press CTRL+SHFT+R for convenience.
Hope this helps.
Related
I am developing an Eclipse RPC application and I have an editor (MainEditor) that contains two pages. The first page (Properties) displays the data of the model using some text fields and the second page (Source) is an editor that is instantiated from a class (SourceCodeEditor) that inherits the CompilationUnitEditor class and displays the source code that contains some annotations and some code.The annotations' values should correspond to the data in the model (the data is stored in variables in the model) .The new class (SourceCodeEditor) does nothing special it only override two super functions and executes the super implementation like so :
#Override
public void doSave(IProgressMonitor progressMonitor) {
super.doSave(progressMonitor);
}
#Override
public void doSaveAs() {
super.doSaveAs();
}
So I would like to add a listener to the instance of the SourceCodeEditor variable that updates the values of the annotations to correspond to the data in the model every time this editor/page is opened. The reason for that is that when I change a text field in the "Properties" page and open the "Source" page without saving the text displays the values before the change not after. If there is a better way to bind the values of the annotations in the source code and the variables of the model,please let me know.
I have a layout .xml file and I want to use it frequently (20 times)within another xml file.
of course with different Initializing.i dont want to use include tags for 20 and init 20 times.Is there any way instead of add include tag for 20 times?
Then create custom view
Create custom class extending any ViewGroup like LinearLayout,RelativeLayout etc and handled your code depending on conditions.
public class YourCustomImpl extends LinearLayout {
...
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.your_common_view, this, true);
...
}
Include your custom implemented view like below
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
...
<your.domain.YourCustomImpl
android:layout_width="match_parent"
android:layout_height="?android:attr/listPreferredItemHeight"/>
</LinearLayout>
For more details there is nice tutorial
I've implemented a custom seekbar preference, using this site - seekbar preference and it works just fine. Now I want to add values to the seekbar's customized properties from the string.xml file instead of hard-coding them:
Instead of writing customseekbar:unitsRight="Seconds" I want to have a string resource like <string name="units">Seconds</string> and use it like this: customseekbar:unitsRight="#string/units". I've tried to implement this guide. My relevant code is:
attrs.xml
<resources>
<declare-styleable name="CustomSeekBarPreference">
<attr name="unitsRight" format="reference|string"/>
</declare-styleable>
And the constructor -
CustomSeekBarPreference.java
public class CustomSeekBarPreference extends Preference implements SeekBar.OnSeekBarChangeListener {
private static final String APPLICATIONNS="http://CustomSeekBarPreference.com";
public CustomSeekBarPreference(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(
attrs, R.styleable.CustomSeekBarPreference, 0 ,0);
mUnitsRight = a.getString(R.styleable.CustomSeekBarPreference_unitsRight);
a.recycle();
}
and the layout -
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:customseekbar="http://CustomSeekBarPreference.com" >
<com.x.sharedpreferencestestapp.CustomSeekBarPreference
android:key="1"
android:defaultValue="30"
android:max="100"
customseekbar:min="0"
android:title="Default step"
customseekbar:unitsRight="#string/units"/>
</PreferenceScreen>
But as you can see, I get 'null' instead of the right value:
Even if I change the value to a fixed string instead of string resource, like customseekbar:unitsRight="Seconds" I still get the same result. And just to make it clear - if I stick to the original code of the seekbar preference: mUnitsRight = getAttributeStringValue(attrs, APPLICATIONNS, "unitsRight", "defaultValue") it works, but not with string resource.
You're getting null for that attribute value because of the namespace you've declared for it in the layout XML - http://CustomSeekBarPreference.com.
As far as I'm aware, the obtainStyledAttributes() method can only pull attributes that are in the standard Android resource namespace – http://schemas.android.com/apk/res/android – or your app's resource namespace, which is http://schemas.android.com/apk/res/ plus the app's package name. Attributes in any other namespace will be ignored, and will not be in the returned TypedArray, which is why you get null no matter if the value is a hardcoded string, or a resource reference.
In the example you're following, they've used a similar non-standard namespace, but they're pulling the value directly from the AttributeSet, specifying that namespace in the getAttributeValue() call thereon. I can't say that I've seen this particular method often used in this manner, and the only benefit I can see to it is that it saves you from having to define your own custom attributes, which is a rather trivial task.
There are a couple of ways to fix this.
Move those layout attributes into your app's namespace, and define an attr resource for each.
This is the method demonstrated in the developer page you've linked, and is probably the most common and familiar way to implement custom View attributes.
First change the namespace declaration in the layout to:
xmlns:customseekbar="http://schemas.android.com/apk/res-auto"
(The res-auto segment is a convenience that will cause the actual namespace name to be appropriately constructed with the current package name, as previously described.)
With your posted setup, this will then cause an error with customseekbar:min, since you've not defined min as an attribute resource (attr) in your app. You can simply define that attr, and then handle it the same way you're handling unitsRight; i.e., retrieve its value from the TypedArray returned from obtainStyledAttributes(). You would do the same for any additional custom attributes you might need.
Modify the getAttributeStringValue() method in the CustomSeekBarPreference example to handle resource references as well.
This may be the simpler option, as far as modifying the given example, but directly accessing the AttributeSet values prevents those values from being adjusted for any theme or style that you might wish to apply. If that's not a concern, then the necessary changes are rather simple.
In the modified method, we just need to first check if the attribute value is a resource value, using AttributeSet#getAttributeResourceValue(). If that method returns a valid identifier, we retrieve the actual value with Resources#getString(). If not, we treat the attribute value as a plain string.
private String getAttributeStringValue(AttributeSet attrs, String namespace,
String name, String defaultValue) {
String value = null;
int resId = attrs.getAttributeResourceValue(namespace, name, 0);
if (resId == 0) {
value = attrs.getAttributeValue(namespace, name);
if (value == null)
value = defaultValue;
}
else {
value = getContext().getResources().getString(resId);
}
return value;
}
Using this method, you would not need to define your custom attributes, and the layout namespace can remain as you have it in the posted snippet.
I am trying to implement button click handling in my Android app.
In the XML layout file that includes my button, I added the following line to my Button XML element:
android:onClick="handleClick"
I also defined a method with the following signature in the Activity that uses this layout:
public void handleClick() { /* ... */ }
However, when I ran my app with this code, it crashed. I was able to fix this crash by updating my method signature to:
public void handleClick(View v) { /* ... */ }
but I don't understand why I am required to include this View parameter?
This is because you might want to use your handleClick method for 2 or more Buttons in your XML.
<Button
android:id="#+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="handleClick"/>
<Button
android:id="#+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="handleClick"/>
In that case it could be unclear which button triggers the callback. The View v helps you identify that e.g.
public void handleClick(View v) {
if (v.getId() == R.id.button1) {
} else if(v.getId() == R.id.button2) {
}
}
The View argument supplied represents the View that received the click event. This may be useful if you reuse the handleClick method for multiple Views (in which case, you could inspect the id of the View passed to the method to determine which View was clicked, as illustrated by Enzokie's answer).
You must include this View parameter when defining your method even if you do not use it in your click logic. This is because reflection is used to locate the method corresponding to the name you supplied in XML, and the method name, parameter count, and parameter types are all required to uniquely define a method in Java. Check out this section of the View source code to see exactly how this reflective lookup works!
The View v is the object of your xml file which is referred in your onCreate method.
To refer any component from xml you have to use v to gets its id of the component.
Condition
You have give id to the component in xml if you want to use onClick in your class file.
Introduction
Huh, this is a tough one. At least, I think so...
To clear things up:
I did not find any answers to my question after searching the internet (using Google).
Everything I found was about people setting up the onClickListener's for their View's wrong. I guess it is the same problem in my case, but none of the other problems matched mine.
This seems to be the same problem... It has no answers.
Setup
I've got three Fragment's set up together with a ViewPager in my AppCompatActivity.
viewPager = (ViewPager) findViewById(R.id.main_view_pager);
viewPager.setAdapter(new SectionsPagerAdapter(getSupportFragmentManager()));
Handled by the SectionsPagerAdapter.
public class SectionsPagerAdapter extends FragmentPagerAdapter {
SectionsPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
return MainTextHolder.newInstance(tabIndicatorOnClick);
case 1:
return PostWriter.newInstance(tabIndicatorOnClick);
case 2:
return TopPage.newInstance(tabIndicatorOnClick);
default:
return Error.newInstance();
}
}
#Override
public int getCount() {
return 3;
}
// ... more methods
}
In each of the Fragment's I have some content plus a custom TabIndicator.
(the following xml file is my View's for the indicator)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<View
android:id="#+id/fragment_divider_one"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:tag="one" />
<View
android:id="#+id/fragment_divider_two"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:tag="two" />
<View
android:id="#+id/fragment_divider_three"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:tag="three" />
</LinearLayout>
And then I set an OnClickListener for each of those View's (dividers) in the Fragment's onCreateView(LayoutInflater inflater, ViewGroup Bundle savedInstanceState) method. The OnClickListener is already prepared in my Activity where I also instantiate my ViewPager so that I can change the current Item (Fragment/tab).
private final View.OnClickListener tabIndicatorOnClick = new View.OnClickListener() {
#Override
public void onClick(View view) {
if (view.getTag().toString().equals("one"))
viewPager.setCurrentItem(0, true); // second argument for smooth transition
else if (view.getTag().toString().equals("two"))
viewPager.setCurrentItem(1, true);
else if (view.getTag().toString().equals("three"))
viewPager.setCurrentItem(2, true);
}
};
I pass that OnClickListener to the Fragment's by putting it into my newInstance(View.OnClickListener tabIndicatorOnClick) method.
This is for one of the fragments. It is identical for the others!
public static MainTextHolder newInstance(View.OnClickListener tabIndicatorOnClick) {
MainTextHolder fragment = new MainTextHolder();
Bundle args = new Bundle();
fragment.putIndicatorTabIndicatorOnClick(tabIndicatorOnClick);
fragment.setArguments(args);
fragment.setRetainInstance(true);
return fragment;
}
My putIndicatorTabIndicatorOnClick(View.OnClickListener tabIndicatorOnClick) method is a Void in an Interface. It just applies the OnClickListener to the Class(Fragment).
#Override
public void putIndicatorTabIndicatorOnClick(View.OnClickListener tabIndicatorOnClick) {
this.tabIndicatorOnClick = tabIndicatorOnClick;
}
Does it work
Yes it does. It works perfectly fine... until a ConfigurationChange happens. In my case I tested it with changing the orientation.
The Problem
What happens after that ConfigurationChange is that everything goes normally. The OnClickListener gets applied to all View's in all Fragment's, but the tabIndicatorOnClick OnClickListener is a null object reference in the second and third Fragment.
So in PostWriter and TopPage there isn't even really an OnClickListener on the View's for what ever reason. But it gets better: In MainTextHolder Fragment the tabIndicatorOnClick is not a null object reference, but it does not change the ViewPager's Item anymore. It runs the code, but it does not scroll the Tab.
When turning on "Don't keep activities" ind Developer options of the device and leaving the app, all OnClickListener's are null object references. Even those in MainTextHolder.
Summary
After the ConfigurationChange the OnClickListener gets passed into my first Fragment, but it is no working properly and in the remaining Fragment's it is not even passed / a null object reference.
The issue can be replicated through destroying the Activity and then reloading it.
In the end...
... I have no clue what is going wrong. I hope that I structured my question properly.
May be of interest
minSdkVersion: 14
targetSdkVersion: 25
I already have a SpringIndicator on my ViewPager. This does not feature an OnClickListener so I added my own "overlayed" indicators which have the needed OnClick feature. I also like the look of it, but if you can give me a better solution for my TabIndicator, I would also love to have that as an answer.
I came across the solution to put the method into the xml :onClick attribute multiple times, but for that I would need to create the same OnClickListener for every Fragment which is not really the nice way and it is also not solving my problem, but a way around it. - I would need to pass the ViewPager into my Fragment and then call that method from xml which is, as I said, just another way of doing it and not the way I prefer to do it. Also I don't know if it works. I'll most likely test it.
Other OnClickListener's in the Fragment still work properly. So as I said the problem lays in the OnClickListener itself not working right/being a null object reference.
Firstly, well done for writing a detailed description of the problem. Many questioners on StackOverflow can learn from the ability of this question to articulate the problem.
This error would seem to come from not accounting for Fragment lifecycle changes. While inside a ViewPager, Fragments will pass through various states including hidden and shown when they are temporarily offscreen, and paused and resumed if they are offscreen and the system frees memory, and finally created and destroyed if the Android system decides to save the instance state of your Activity (such as from a configuration change). In the latter case, Android will try and restore the state of your Fragments from saved instance state in your ViewPager. Since your Fragments have no way to save the View.OnClickedListener they were passed in the arguments, they end up with a null pointer when they are restored which is causing your error.
To fix the error, I suggest that you do not pass the View.OnClickedListener as a parameter to the Fragments. Rather, expose the OnClickListener to your Fragments via a public method in your Activity and have the Fragments get it themselves in their onResume(). This way you can guarantee that the Fragments will have a reference to the View.OnClickedListener whenever they are in a resumed state. So your onResume() might look something like this:
#Override
public void onResume() {
OnClickListener onClickListener = ((MyActivity) getActivity).getOnClickListener();
putIndicatorTabIndicatorOnClick(onClickListener);
}
Since you have set it in onResume(), you should remove it in onPause():
#Override
public void onPause() {
//set the onClickListener to null to free up the resource
}
Using onResume() and onPause() like this to free up resources from listeners is the approach recommended in the developer guide.
Of course, you will have to make sure your Activity also handles its own lifecycle by checking for saved state in onCreate(Bundle savedInstanceState) and restoring it appropriately.
Please note that saving instance state and restoring can be triggered in a number of ways:
Configuration state change (such as rotating the phone and causing the Activity to display in landscape rather than portrait
The device becoming low on memory (for example, if a large number of Activities are in recent apps)
Turning on Developer Options / Don't keep activities and using home to put your application in recent apps and then navigating back to your app.
You will be able to use one of these methods to determine if you have correctly handled the lifecycles of your Activities and Fragments.