I'm working in my Android project developing with a HTC Desire (Gingerbread 2.3.7) and a Google Nexus 7 (Jelly Bean 4.3). I need to send some data from MainActivity to InfoActivity, so I use an intent. In this InfoActivity, I also have a menu item in the action bar to refresh the info.
In InfoActivity I show the data to the user. But this is not the problem, the problem is with the menu. Look at the following code:
public class ShowInfoActivity extends ActionBarActivity {
private MenuItem menuItem = null;
// ...
#Override
protected void onCreate(Bundle savedInstanceState) {
// ...
new OneTask().execute(...);
// ...
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
case R.id.refresh:
menuItem = item;
return true;
default:
return super.onOptionsItemSelected(item);
}
}
private class OneTask extends AsyncTask<Object, Void, String> {
// ...
#Override
protected void onPreExecute() {
MenuItemCompat.setActionView(menuItem,
R.layout.actionbar_indeterminate_progress);
MenuItemCompat.expandActionView(menuItem);
}
// ...
#Override
protected void onPostExecute(String result) {
MenuItemCompat.collapseActionView(menuItem);
MenuItemCompat.setActionView(menuItem, null);
}
}
Obviously, the first time it's executed, menuItem=null, so it must crash. Incredibly, in HTC it works fine but in Nexus it obviously crashes. Why is this different between devices?
PS: I already solved it, but I want to know why this behaviour...
When in doubt, always check the source code. If you look at MenuItemCompat.java you'll find that it switches based on the API level like so:
static final MenuVersionImpl IMPL;
static {
final int version = android.os.Build.VERSION.SDK_INT;
if (version >= 14) {
IMPL = new IcsMenuVersionImpl();
} else if (version >= 11) {
IMPL = new HoneycombMenuVersionImpl();
} else {
IMPL = new BaseMenuVersionImpl();
}
}
The base setActionView method for the base implementation (which is used for 2.3 devices) just returns the MenuItem, so it wouldn't ever throw the exception:
#Override
public MenuItem setActionView(MenuItem item, View view) {
return item;
}
The HoneycombMenuVersionImpl, on the other hand, delegates to another class:
#Override
public boolean setShowAsAction(MenuItem item, int actionEnum) {
MenuItemCompatHoneycomb.setShowAsAction(item, actionEnum);
return true;
}
And the delegate class attempts to call the actual method on the MenuItem, which will throw an exception:
public static void setShowAsAction(MenuItem item, int actionEnum) {
item.setShowAsAction(actionEnum);
}
In this particular example, checking the source code answers your question and shows you a solid strategy for dealing with compatibility across different versions of Android.
setActionView()/collapseActionView() are native functions in Android 4 ... passing null MenuItem will surely be frowned upon.
On your Android 2.3 device, the compatibility library (android.support.v4) has it's own internal implementations of setActionView()/collapseActionView() that are likely more resilient to bad input.
Related
I am relatively new to StackOverflow and Android Studio so apologies for newbie question!
I recently switched over to Android Studio from Eclipse.
I am noticing that when one function in one of my source files has an error that ALL functions in that file on the project view pane on the top left are showing the red squiggly so it is a little more time consuming to actually get to the error. Any ideas as to why this is happening?
Thank you
As Shlublu points out, it is difficult to say without an example. Here is one possible situation. Take this class:
public class MyClass {
private int myNumber;
public MyClass( int number) {
this.myNumber = number;
}
public int getAnswer() {
return this.myNumber;
}
private void someMethod() {
int x = 0;
} // remove this } and all below is an error
#Override
public String toString() {
return String.valueOf(this.myNumber);
}
#Override
public boolean equals(Object obj) {
return true;
}
}
Removing the } at the end of the someMethod() causes all methods below that to have an error.
I am using the library for one of my project (https://github.com/googlemaps/android-maps-utils)
This library let me create cluster on the google map, but I was wondering if it is possible to cluster my marker by group. For example, I want to cluster only markers that are "Friends" and cluster the others who are only "Coworker" and etc... (Maybe not the best example, but I hope that you understand)
My idea was to use multiple ClusterManager but I didn't tried it and don't really know if it is the best solution or even a good solution.
I found the solution for my problem. The better solution is to manage multiple Clustermanagers if you want to create multiple group.
By the way, all the credits for the answer goes to #Brody on the Github :
Here the link : https://github.com/googlemaps/android-maps-utils/issues/100#event-153755438
Using multiple ClusterManager is cumbersome. I think it is easier to use multiple Algorithm with a wrapper.
The wrapper should choose the correct algorithm according to the item properties. The only requirement is that all items must have a common parent class (Item in the example below).
public class MultiAlgorithm<T extends ClusterItem> implements Algorithm<T> {
private final Algorithm<T> friendsAlgorithm;
private final Algorithm<T> coworkerAlgorithm;
public MultiAlgorithm() {
friendsAlgorithm = new NonHierarchicalDistanceBasedAlgorithm<>();
coworkerAlgorithm = new NonHierarchicalDistanceBasedAlgorithm<>();
}
private Algorithm<T> getAlgorithm(T item) {
// TODO Return the correct algorithm based on 'item' properties
}
#Override
public void addItem(T item) {
getAlgorithm(item).addItem(item);
}
#Override
public void addItems(Collection<T> collection) {
for (T item : collection) {
getAlgorithm(item).addItem(item);
}
}
#Override
public void clearItems() {
friendsAlgorithm.clearItems();
coworkerAlgorithm.clearItems();
}
#Override
public void removeItem(T item) {
getAlgorithm(item).removeItem(item);
}
#SuppressWarnings("unchecked")
#Override
public Set<? extends Cluster<T>> getClusters(double zoom) {
// Use a non-typed Set to prevent some generic issue on the result.addAll() method
Set result = new HashSet<>(friendsAlgorithm.getClusters(zoom));
result.addAll(coworkerAlgorithm.getClusters(zoom));
return result;
}
#Override
public Collection<T> getItems() {
Collection<T> result = new ArrayList<>(friendsAlgorithm.);
result.addAll(coworkerAlgorithm.getItems());
return result;
}
}
Usage: clusterManager.setAlgorithm(new MultiAlgorithm<Item>());
I need to determine in runtime from code if the application is run under TestInstrumentation.
I could initialize the test environment with some env/system variable, but Eclipse ADK launch configuration would not allow me to do that.
Default Android system properties and environment do not to have any data about it. Moreover, they are identically same, whether the application is started regularly or under test.
This one could be a solution: Is it possible to find out if an Android application runs as part of an instrumentation test but since I do not test activities, all proposed methods there won't work. The ActivityManager.isRunningInTestHarness() method uses this under the hood:
SystemProperties.getBoolean("ro.test_harness")
which always returns false in my case. (To work with the hidden android.os.SystemProperties class I use reflection).
What else can I do to try to determine from inside the application if it's under test?
I have found one hacky solution: out of the application one can try to load a class from the testing package. The appication classloader surprisingly can load classes by name from the testing project if it was run under test. In other case the class is not found.
private static boolean isTestMode() {
boolean result;
try {
application.getClassLoader().loadClass("foo.bar.test.SomeTest");
// alternatively (see the comment below):
// Class.forName("foo.bar.test.SomeTest");
result = true;
} catch (final Exception e) {
result = false;
}
return result;
}
I admit this is not elegant but it works. Will be grateful for the proper solution.
The isTestMode() solution did not work for me on Android Studio 1.2.1.1. Almighty Krzysztof from our company tweaked your method by using:
Class.forName("foo.bar.test.SomeTest");
instead of getClassLoader(). Thanks for Krzysztof!
We created a solution to pass parameters to the MainActivity and use it inside the onCreate method, enabling you to define how the Activity will be created.
In MainActivity class, we created some constants, which could also be an enum. We created a static attribute too.
public class MainActivity {
public static final int APPLICATION_MODE = 5;
public static final int UNIT_TEST_MODE = 10;
public static final int OTHER_MODE = 15;
public static int activityMode = APPLICATION_MODE;
(...)
#Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
switch (activityMode) {
case OTHER_MODE:
(...)
break;
case UNIT_TEST_MODE:
Log.d(TAG, "Is in Test Mode!");
break;
case APPLICATION_MODE:
(...)
break;
}
(...)
}
(...)
}
We made MainActivityTest class abstract, created a setApplicationMode and called this method inside the setUp() method, before calling the super.setUp() method.
public abstract class MainActivityTest extends ActivityInstrumentationTestCase2<MainActivity> {
protected void setUp() throws Exception {
setApplicationMode(); // <=====
super.setUp();
getActivity();
(...)
}
(...)
public void setApplicationMode() {
MainActivity.activityMode = MainActivity.UNIT_TEST_MODE;
}
}
All other test classes inherit from MainActivityTest, if we want it to have another behaviour, we can simply override the setApplicationMode method.
public class OtherMainActivityTest extends MainActivityTest {
(...)
#Override
public void setApplicationMode() {
MainActivity.activityMode = MainActivity.OTHER_MODE;
}
}
The user nathan-almeida is the friend that is co-author of this solution.
I always have this problem of java.lang.IllegalStateException:Could not execute method of the activity. I was planning to perform an android component event (ex. Button event - indicating the number of times this button was clicked). Here's the code snippet for this problem:
interface Selection {
public void clicked();
}
public class ParentClass extends FragmentActivity {
// fTabs : FragmentTabHost
// tabs : Map<String, Selection>
private void initialize() {
// fistFrag : FirstChildClass = new FirstChildClass()
// secondFrag : SecondChildClass = new SecondChildClass()
tabs.put("first", firstFrag);
tabs.put("second", secondFrag);
fTabs.add(fTab.newTabSpec("first").setTitle("First"), firstFrag.getClass(), null)
fTabs.add(fTab.newTabSpec("second").setTitle("Second"), secondFrag.getClass(), null)
}
#Override
public void onBackPressed() {
tabs.get(fTabHost.getCurrentTabTag()).clicked();
}
}
public class FirstChildClass extends Fragment implements Selection {
// data : TextView
// hit : int = 0
#Override
public void clicked() {
data.setText(String.format("Hit Count: %d", ++hit));
}
}
public class SecondChildClass extends Fragment implements Selection {
// data : TextView
// hit : int = 0
#Override
public void clicked() {
data.setText(String.format("Hit Count: %d", ++hit));
}
}
I've tried to assure of clicked() works view interfacing approach by invoking a message on Logcat and it worked but when I used Button the error above always prompts me. I've checked if data is null and it returned true. I am a little bit confused, I've tried to check nullity of data from the Activity methods is returns false but when I access any method override by an interface it always return true. Is there a way to solve this?
Here's a way my friend told me to solve this problem. Using getSupportFragmentManager. He told me also that creating an Activity or Fragment using its constructor isn't applicable on the Android platform. So I switched by to the conventional way of adding tabs to FragmentTabHost.
#Override
public void onBackPressed() {
//tabs.get(fTabHost.getCurrentTabTag()).clicked();
((Selection) getSupportFragmentManager().findByFragmentByTag(fTabHost.getCurrentTabTag()).clicked();
}
Particularly the Navigation between the Views over the ViewModels looks like that:
ShowViewModel<InfoViewModel>();
Or between normal Activities:
context.StartActivity(typeof(InfoActivity));
The problem I face now (actually I solved it in one of my latest projects by using Tinymessenger - its an event aggregator/messenger for loosely coupled communication) but I would like to know if theres an other way!
I'm creating an optionsmenu:
public abstract class BaseActivityWithoutTabs<T> : MvxActivity where T : class, IMvxViewModel
{
public override bool OnCreateOptionsMenu(IMenu menu)
{
return ActivitiesHelper.CreateOptionsMenu(menu);
}
public override bool OnOptionsItemSelected(IMenuItem item)
{
return ActivitiesHelper.CreateOnOptionsItemSelectedEvent(item, this);
}
}
InfoActivity derives from this BaseActivityWithoutTabs.
In the ActivitiesHelper Class (from above code) I'm creating the menu and the events:
public class ActivitiesHelper
{
private const int einstellungenItemId = 0;
private const int infoItemId = 1;
public static bool CreateOptionsMenu(IMenu menu)
{
// GroupId, ItemId, OrderId
menu.Add(0, einstellungenItemId, 0, "Einstellungen").SetIcon(Android.Resource.Drawable.IcMenuManage);
menu.Add(0, infoItemId, 1, "Info").SetIcon(Android.Resource.Drawable.IcMenuInfoDetails);
return true;
}
public static bool CreateOnOptionsItemSelectedEvent(IMenuItem item, Context context)
{
var id = item.ItemId + 1; // (Id is zero-based :)
if (id == 1) // First Item
{
context.StartActivity(typeof(SettingsShowActivity));
}
else if (id == 2) // Second Item
{
context.StartActivity(typeof(InfoActivity)); //doesn't work...
}
return true;
}
}
As you see I do here "StartActivity".. it works for the first "SettingsShowActivity" but thats an PreferenceActivity, so there no reason why it should fail. The problem is, that I would like to Start here the InfoActivity(as you see in code - Second Item) and this doesn't work. It opens the Activity but the List doesn't gets filled.
But if I go to a ViewModel in my project and call: ShowViewModel<InfoViewModel>();it works fine but this is on that place (in the ActivitiesHelper Class) not available/possible!
public class InfoViewModel : MvxViewModel
{
public InfoViewModel()
{
Info info = new Info();
info.Key = "ITS A KEYY";
info.Value = "here we got a value";
ObservableCollection<Info> asd = new ObservableCollection<Info>();
asd.Add(info);
Infos = asd;
}
private ObservableCollection<Info> infos = new ObservableCollection<Info>();
public ObservableCollection<Info> Infos
{
get
{
return infos;
}
set
{
infos = value;
RaisePropertyChanged(() => Infos);
}
}
}
Any suggestions?
I have no idea what you are talking about. Seriously, you've just dumped a lot of stuff on the screen.
I think you've gotten yourself very confused - good luck trying to work out what on earth you've done.
One basic answer is that you can navigate to an Mvx-based Activity anywhere you want to simply by:
creating an MvxViewModelRequest - https://github.com/slodge/MvvmCross/blob/v3/Cirrious/Cirrious.MvvmCross/ViewModels/MvxViewModelRequest.cs
converting the request to an Intent using the IMvxAndroidViewModelRequestTranslator singleton - https://github.com/slodge/MvvmCross/blob/v3/Cirrious/Cirrious.MvvmCross.Droid/Views/IMvxAndroidViewModelRequestTranslator.cs
starting that Intent
However, I seriously suggest you step out of your current mess and consider a cleaner application flow.