I'm looking to write preferences that can be applied to both 3.0 and pre-3.0 devices. Discovering that PreferenceActivity contains deprecated methods (although these are used in the accompanying sample code), I looked at PreferenceFragement and the compatibility package to solve my woes.
It appears, though, that PreferenceFragment isn't in the compatibility package. Can anyone tell me whether this was intentional? If so, can I easily target a range of devices (i.e. < 3.0 and >=3.0) or will I have to jump through hoops? If it wasn't intentionally excluded, can we expect a new release of the compatibility package? Or is there another workaround that is safe to use?
Discovering that PreferenceActivity contains deprecated methods (although these are used in the accompanying sample code)
The deprecated methods are deprecated as of Android 3.0. They are perfectly fine on all versions of Android, but the direction is to use PreferenceFragment on Android 3.0 and higher.
Can anyone tell me whether this was intentional?
My guess is it's a question of engineering time, but that's just a guess.
If so, can I easily target a range of devices (i.e. < 3.0 and >=3.0) or will I have to jump through hoops?
I consider it to be done "easily". Have two separate PreferenceActivity implementations, one using preference headers and PreferenceFragments, the other using the original approach. Choose the right one at the point you need to (e.g., when the user clicks on the options menu item). Here is a sample project demonstrating this. Or, have a single PreferenceActivity that handles both cases, as in this sample project.
If it wasn't intentionally excluded, can we expect a new release of the compatibility package?
You will find out when the rest of us find out, which is to say, if and when it ships.
Or is there another workaround that is safe to use?
See above.
The subtle implication of the answer from #CommonsWare is that - your app must choose between the compatibility API or the built-in fragment API (since SDK 11 or so). In fact that's what the "easily" recommendation has done. In other words, if you want to use PreferenceFragment your app needs to use the built-in fragment API and deal with the deprecated methods on PreferenceActivity. Conversely, if it's important that your app use the compat. API you will be faced with not having a PreferenceFragment class at all. Thus, targeting devices is not a problem, but the hoop-jumping happens when you have to choose one or the other API and thus submit your design to unforeseen workarounds. I need the compat. API so I'm going to create my own PreferenceFragment class and see how that works. In the worst case scenario I'll just create a normal (fragment) layout and bind the view components to the sharedprefs manually...ugh.
EDIT:
After trying and looking at the code at http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.0.1_r1/android/preference/PreferenceFragment.java?av=h -- creating my own PreferenceFragment isn't going to happen. It appears the liberal use of package-private in PreferenceManager instead of 'protected' is the main blocker. It really doesn't look like there's any security or really good motivation to have done that and it isn't great for unit-testing but oh well...less typing I guess...
EDIT v2:
Actually it did happen and it worked. It was definitely a headache to make the code work with the Compatibility API JAR. I had to copy about 70% the com.android.preference package from the SDK to my app and then wrestle with typically mediocre-quality Java code in Android. I used v14 of the SDK. It would have been much easier for a Goog engineer to do what I did, contrary to what I've heard some lead Android engineers say about this topic.
BTW - did I say "targeting devices is not a problem"? It totally is...if you use com.android.preference you are not going to be able to swap out with the Compatibility API without major refactoring. Fun log!
Building upon CommonsWare's answer as well as Tenacious' observations, I have come up with a single descendant class solution capable of targeting all current Android API versions with minimal fuss and no code or resource duplication. Please see my answer to the related question over here:
PreferenceActivity Android 4.0 and earlier
or on my blog:
http://www.blackmoonit.com/2012/07/all_api_prefsactivity/
Tested on two tablets running 4.0.3 and 4.0.4 as well as a phone running 4.0.4 and 2.3.3 and also an emulator running 1.6.
See PreferenceFragment-Compat from Machinarius. It was easy to drop in with gradle and I forget that it's even there.
compile 'com.github.machinarius:preferencefragment:0.1.1'
Important Update: The latest revision of the v7 support library now has a native PreferenceFragmentCompat.
On August 2015 Google released the new Preference Support Library v7.
Now you can use the PreferenceFragmentCompat with any Activity or AppCompatActivity
public static class PrefsFragment extends PreferenceFragmentCompat {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Load the preferences from an XML resource
addPreferencesFromResource(R.xml.preferences);
}
}
You have to set preferenceTheme in your theme:
<style name="AppTheme" parent="#style/Theme.AppCompat.Light">
...
<item name="preferenceTheme">#style/PreferenceThemeOverlay</item>
</style>
In this way you can customize the preferenceTheme to style the layouts used for each preference type without affecting other parts of your Activity.
Tenacious's answer is correct, but here are some more details.
The reason you can't "create a normal layout and bind the view components to the sharedprefs manually" is that there are some surprising omissions in the android.preferences API. PreferenceActivity and PreferenceFragment both have access to critical non-public PreferenceManager methods, without which you can't implement a preference UI of your own.
In particular, to construct a Preference hierarchy from an XML file you need to use a PreferenceManager, but all of PreferenceManager's constructors are either package-private or hidden. The method of attaching the Preference onClick listeners to your activity is also package-private.
And you can't work around this by sneakily putting your implementation in the android.preferences package, because non-public methods in Android APIs are actually omitted from the SDK. With a bit of creativity involving reflection and dynamic proxies, you can still get at them. The only alternative, as Tenacious says, is to fork the entire android.preference package, including at least 15 classes, 5 layouts, and a similar number of style.xml and attrs.xml elements.
So to answer the original question, the reason Google didn't include PreferenceFragment in the compatibility package is that they would have had exactly the same difficulty as Tenacious and myself. Even Google can't go back in time and make those methods public in the old platforms (though I hope they do that in future releases).
My app target is API +14 but due to using support library for some fancy navigation, I couldn't use the android.app.Fragment and had to use android.support.v4.app.Fragment, but I also needed to have PreferenceFragment in place without large changes to code behind.
So my easy fix for having both worlds of support library and PreferenceFragment:
private android.support.v4.app.Fragment fragment;
private android.app.Fragment nativeFragment = null;
private void selectItem(int position) {
fragment = null;
boolean useNativeFragment = false;
switch (position) {
case 0:
fragment = new SampleSupprtFragment1();
break;
case 1:
fragment = new SampleSupprtFragment2();
break;
case 2:
nativeFragment = new SettingsFragment();
useNativeFragment = true;
break;
}
if (useNativeFragment) {
android.app.FragmentManager fragmentManager = getFragmentManager();
fragmentManager.beginTransaction()
.replace(R.id.content_frame, nativeFragment).commit();
} else {
if (nativeFragment != null) {
getFragmentManager().beginTransaction().remove(nativeFragment)
.commit();
nativeFragment = null;
}
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction()
.replace(R.id.content_frame, fragment).commit();
}
}
I needed integrate Preferences into application's design and keep support for 2.3 android. So I still needed PreferencesFragment.
After some search I found android-support-v4-preferencefragment lib. This lib save a lot of time for copying and refactoring original PreferencesFragment as Tenacious said. Works fine and users enjoy preferences.
Related
I know what setDisplayHomeAsUpEnabled does, but what is setDefaultDisplayHomeAsUpEnabled for I can only wonder. No documentation found, cannot find anything except it is being used.
This method is only available in the Support Action Bar, not in the "native" ActionBar class available since Android 3. More importantly, it is annotated with #hide in the source, meaning it is not part of the official API for third-party developers. That is why it is nowhere documented by Google. You should just not use it.
Having a deeper look into the sources, I found the method implemented in WindowDecorActionBar:
public void setDefaultDisplayHomeAsUpEnabled(boolean enable) {
if (!mDisplayHomeAsUpSet) {
setDisplayHomeAsUpEnabled(enable);
}
}
So basically it does exactly the same as using setDisplayHomeAsUpEnabled, but only if the value has not yet been set manually using the said function.
tldr: you should always use setDisplayHomeAsUpEnabled and ignore the default method.
I am porting an Xamarin-based Android app to be PCL'ified and MvvmCross'ified so we can target multiple mobile device platforms.
Since this is a line-of-business app where we can dictate the minimum Android OS version, we wish to avoid using the various v4 / v7 etc "Support libraries". We prefer to use all native components that are already part of the OS. So this basically means we only wish to target Jellybean and probably Android v4.2 upwards.
Unfortunately whilst there are lots of examples out there of using MvvmCross with ActionBarSherlock and the SupportActionBar (in a support library), there appears to be no examples of how to wire up MvvmCross with the native ActionBar using native Fragments as tabs.
I'm not expecting someone to post up swathes of code but would really appreciate if someone could give me a few pointers on the best approach to take to achieve this.
I will certainly blog about it once I have a working solution to benefit the whole MvvmCross/Xamarin community.
Thanks.
Probably the most important thing is to use MvvmCross - Hot Tuna Droid Full Fragment Support NUGet library. Then create activity that inherits from MvxActivity and fragment from MvxFragment.
ViewModel for tab fragment set in activity when create this class for TabEventArgs.FragmentTransaction.Add method used in ActionBar.Tab.TabSelected event handler like this:
var tab1 = new TabFragment1 {DataContext = ViewModel};
In TabFragment1 class you can use standard fluent binding method on bindingSet creted by
var bindingSet = this.CreateBindingSet<TabFragment1, MyViewModel>();
I don't know if it is a right way, but work.
I included paypal sdk in the app and set the product name and price as shown in the attached image.
Is it possible to change the title of the paypal PaymentActivity class . ?
I used the entire code for paypal from this link
.Please suggest whether we could change the title to any text ,(I want to change the text that is marked in Red Circle)?
Jeff here from the PayPal Mobile SDK team.
The PayPal Android SDK doesn't support modifying any of the SDK activities. We don't plan to allow the ability to modify titles, as this would be an inconsistent user experience. However, we're open to feedback from users, so please feel free to file issues in GitHub if you have feature requests!
Couple observations, apparently:
The activity of off which you have put screenshot is PaymentMethodActivity (according to Hierarchy Viewer)
PayPal SDK uses ABS
According to the sample app provided in PayPal SDK page, onBuyPressed() launches PaymentActivity with startActivityForResult():
Now, according to SDK references, PaymentActivity does not provide any API for setting custom title. The page lists setTitle() as a part of "Methods inherited from class android.app.Activity", but does not give any details on how to extend PaymentActivity so that one could leverage those APIs.
So, currently, there is no way to use a custom title.
I personally would not bother to change the title on that screen as it clearly depicts itself as a PayPal screen (as one would see their homepage on a browser), but if you really want a custom title, you may need to file a new issue on their GitHub page.
Your AndroidManifest.xml file should have an entry for the PaymentActivity that looks like this:
<activity android:name="com.paypal.android.sdk.payments.PaymentActivity" />
All you should have to do is add the label attribute to that to change the title:
<activity android:name="com.paypal.android.sdk.payments.PaymentActivity" android:label="#string/some_title" />
For other potentially useful attributes, refer to the activity tag documentation.
Note: I'm assuming here that the PayPal SDK will allow you to override the title. That is, I would expect it to only set a default title if none is given explicitly.
Edit: Unfortunately, the assumption above has proven to be incorrect. Diving a little deeper, it turns out every activity in the PayPal SDK always sets its title in the onCreate() method. That's actually a little surprising, because that means that PayPal isn't leveraging Android's built-in support for localisation and resources. Although I suppose it does allow them to offer developers the SDK as a jar, which doesn't support bundling Android resources.
In any case, since the various activities haven't been declared final, you could simply try to extend them and set the title after the super has done its work:
public class TitlePaymentMethodActivity extends PaymentMethodActivity {
#Override public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setTitle("Custom Title")
// or:
setTitle(R.string.custom_title);
}
}
After that, you'll have to change the manifest to point to your TitlePaymentMethodActivity, in stead of directly to the activity in the SDK. Do note that this approach may break at any point, if PayPal decides to finalize the activity classes. With that in mind, I suggest you follow #shoe rat's recommendation and file a request with PayPal for 'official' support.
So I just had to up my min API level to 11 for the Preference (addpreferenceresource was depreciated) - turns out 9-10 takes out like 50% of the market. So my question is, is it better to just suppress the warning to cater to the Gingerbread market or is there a different way to make my preference reference without using Preference Fragments?
I would implement both types (the one that works in 11+ and the one that works in 10-), then use conditional checks for them. This is written in quite a bit of detail in this answer.
Basically, you end up setting up OtherPreferencesActivity with PreferenceFragment, and then PreferencesActivity with the deprecated PreferenceActivity. (Your APK will not break by including this deprecated code, as long as you use a version check so that if/when it's removed in the future, it doesn't try to find it.)
if (Build.VERSION.SDK_INT < 11) {
startActivity(new Intent(this, PreferencesActivity.class);
} else {
startActivity(new Intent(this, OtherPreferencesActivity.class);
}
Keep in mind, you will want to have them use each others' methods as much as possible so that you don't end up duplicating code.
Last tip: #TargetApi(11) and #SuppressWarnings("deprecation") will come in handy here. Just be careful that you're not ignoring other deprecations by doing so.
"depreciated" doesn't mean you can't use it and will break your system if you run the code. It means that it is officially not recommended to use and this method maybe removed from the api. In the future but we don't know when. So I would say it is save to use in this case for now.
Since Android introduced library projects, I've been converting my app into a library so that I can make several versions with appropriate tweaks (for example, a free and pro version using the same code base, but changing a few things).
I initially had trouble allowing the library project's code access to fields in my sub-projects. In other words, my free and pro versions each had a class with a few constants in them, which the library project would use to distinguish certain features.
In the sub-project, I extended the library's main activity and added a static initialisation block which uses reflection to change the values of fields in the library.
public class MyMainActivityProVersion extends MyMainActivity {
public static final String TAG = Constants.APP_NAME + "/SubClass";
static {
try {
ConstantsHelper.setConstants(Constants.class);
} catch (Exception e) {
Log.d(TAG, "--- Constants not initialised! ---");
e.printStackTrace();
}
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
}
In this code, ConstantsHelper is in the library, and I am providing Constants.class from my sub-project. This initialises the constants in the library project.
My approach works great, except for one particular use case. When the app hasn't been used in a while and it is 'stopped' by the OS, the static fields in ConstantsHelper are forgotten.
The constants are supposed to be reset by the main activity (as shown above), but the main activity isn't even launched because the OS resumes a different activity. The result of this is that the initialisation of the constants is forgotten and I cannot re-initialise them because the resumed activity is in the library (which has no knowledge of the sub-project).
How can I 'tell' other activities in the library to call code from sub-projects on resuming? Alternatively, is there a way to ensure that some code in my sub-project is called on every resume?
I think you're "cheating" by trying to share data across two Activities through static members. This happens to work when they're in the same, or related, classloaders. Here I believe Android uses separate classloaders for separate Activities, but, child Activities are in child classloaders. So ViewActivity happens to be able to see up into the parent classloader and see statics for the parent. Later I believe that parent goes away, and so your child re-loads MyMainActivity locally when you next access it and it's not initialized as you wanted. (Well, if it's not that, it's something very like this explanation.)
I think there are some more robust alternatives. You could use the LicenseChecker API to decide whether you're in a free or for-pay version rather than rely on details of the activity lifecycle and classloader. That's probably going to be better as it protects you from other types of unauthorized use.
I'm afraid I never found a good answer to this question. I'll probably continue with my terrible use of reflection and figure out some hacky workaround.
I felt I should come back and at least point out that I didn't solve this for the benefit of others who come to this page.
You can resolve this using Android resources.
Basically, define your constants in a resources xml values file in your Library project
E.g. "lib project"\values\constants.xml
<resources xmlns:tools="http://schemas.android.com/tools">
<bool name="const_free_version">false</bool>
<string name="const_a_constant">pippo</bool>
</resources>
Then, in your sub-project you can redefine the lib-project values using a different resources xml values file:
E.g. "sub project"\values\constants.xml
<resources xmlns:tools="http://schemas.android.com/tools">
<bool name="const_free_version">true</bool>
</resources>
In your lib project code when you refer to R.bool.const_free_version you get the actual value based on sub-project constant values xml.
Note that you don't have to redefine every values defined in the lib project constants.xml but only the ones you need different in your sub project.