I'm developing a game with turn based multiplayer support. To fetch a list of the current games of a player I use the GamesClient.loadTurnBasedMatches method. This works fine but when I try to open a new activity and pass a match to it crashed. The code I use to launch the new activity is
private void openMatch(TurnBasedMatch match) {
Intent intent = new Intent(this, MultiPlayerGame.class);
intent.putExtra("match", match);
startActivity(intent);
}
But after executing this code I get this error: (It is limited like this so I can't see the entire package name)
02-03 21:28:02.880: E/AndroidRuntime(5513): java.lang.RuntimeException: Unable to start activity ComponentInfo{com.mypackage.MultiPlayerGame}: android.os.BadParcelableException: Parcelable protocol requires a Parcelable.Creator object called CREATOR on class com.google.android.gms.games.multiplayer.turnbased.a
The line where it actually crashes is:
match = getIntent().getExtras().getParcelable("match");
Now I'm not sure if this is a fault of mine or is there a bug somewhere in the play-services-lib. Or is it related to proguard? Btw, if I use the built in intent to show the user his games the same method works. (in onActivityResult)
if (request == RC_LOOK_AT_MATCHES) {
TurnBasedMatch match = data.getParcelableExtra(GamesClient.EXTRA_TURN_BASED_MATCH);
if (match != null)
openMatch(match);
}
Your TurnBasedMatch must implement Parcelable contract. See examples here or here
I managed to work around it by only sending the matchId to the next activity. In that activity I use GamesClient.getTurnBasedMatch. It might be that the default intent does this in the backgroud but I'm not sure.
Try
intent.putExtra("match", new TurnBasedMatchEntity(match));
TurnBasedMatchEntity is a class that implements TurnBasedMatch and supports being sent as a Parcelable. You can retrieve it the same way as before.
TurnBasedMatch match = getIntent().getExtras().getParcelable("match");
TurnBasedMatch (and all of its implementations) implement Freezable<>, which has a freeze() method. Calling it will get you an instance that's suitable for serialization.
I suspect (but haven't confirmed) that the call to freeze() simply returns a TurnBasedMatchEntity object. The nice thing about this solution is you don't have to care; all you have to care about is what's defined in the interface.
As a side note, the TurnBasedMatch interface extends Parcelable, which should mean every implementation is also parcelable... but it appears TurnBasedMatchRef doesn't actually implement it (hence the exception). Implementations shouldn't violate their interfaces like this, but that appears to be how it is.
Related
I'm trying to write a unit test for some Android code that looks for a specific key being present in an Intent object. As part of my test, I'm creating an Intent object and adding a String to it. However, when I use the below code, my variable is initalized to null:
val data = Intent().putExtra("key", "value")
// data is null
If I split this up into two lines, it works just fine:
val data = Intent()
data.putExtra("key", "value")
// data is non-null and contains my key/value
What feature of the Kotlin language is causing this to happen?
Note that putExtra() returns an Intent object. From the Android source:
public #NonNull Intent putExtra(String name, String value) {
if (mExtras == null) {
mExtras = new Bundle();
}
mExtras.putString(name, value);
return this;
}
In the first case, the inferred type is Intent!. I was under the impression that this just means that it's an Intent or an Intent? but Kotlin doesn't want to make devs go crazy with Java platform types. Still, given that putExtra() returns a non-null value, I'd expect the actual value of data to be non-null.
The short answer is what #CommonsWare and #TheWanderer mentioned in comments: my test class was in the test/ directory, so it was using a mock Intent implementation instead of the real thing.
When I move my test to the androidTest/ directory, everything works as expected. The observed behavior has nothing to do with Kotlin.
Some extra info about why this was so confusing...
First, I was mistaken when I wrote this:
val data = Intent()
data.putExtra("key", "value")
// data is non-null and contains my key/value
The data variable was non-null, but it did not actually contain my key/value pair. The mock Intent implementation I was using was dropping the putExtra() call.
So, why was my test passing?
The one particular test I decided to dig deeper on was testing the negative case (when a key other than the one it expects is present in the Intent). But I wasn't passing an Intent with the wrong key, I was passing an Intent with no keys at all. Either way, though, the expected key is not present, and the method returns false.
The positive case (where the required key actually was passed to putExtra()) failed with an AssertionError. Too bad I didn't pick this one to scrutinze.
My main project has apparently stubbed Intent.putExtra() as a no-op, via the returnDefaultValues = true gradle option. When I create a new project and try to reproduce this issue, I get a very clear error:
java.lang.RuntimeException: Method putExtra in android.content.Intent not mocked. See http://g.co/androidstudio/not-mocked for details.
at android.content.Intent.putExtra(Intent.java)
at com.example.stackoverflow.IntentTest.test(IntentTest.kt:12)
Unfortunately, with the mocked putExtra(), I never got this helpful message.
I've been fiddling around with sample codes and ran across a snippet and tried using it but problem is I don't know how to call this kind of method from the same activity I declared it in. The snippet only showed this kind of method and not how to call it. I don't even know what this is defined as so it's been hard finding the answer, a method with multiple parameters I guess?
This is the method I want to call, it's linked to another class Payments.
void Calculate(Context con, Payments Pay)
I've tried the usual Calculate() but it tells me that Calculate(Context,Payments) cannot be applied to ();
Can anyone explain what's going on ?
Generate an object of the class Payments:
Payments pay = New Payment(Paramters);
Then call calculate with the getApplicationContext and the Payment object:
Calculate(getApplicationContext(), pay);
I hope it helps.
you have to pass context means activity instance & instance of payment class
Like
Context con = getActivity();
Payments Pay = new Payments();
Calculate(con,pay);
//removed the "void"
I hope this help you
I use a third-party API (JAudioTagger) and I would like to start an activity with an object of this API (AudioFile).
The problem is this object does not implement Parcelable or Serializable.
What is the best way to do this ?
EDIT
Google's answer : http://developer.android.com/guide/faq/framework.html
You have a few options, none of which are easy or perfect (depending on the object and use-case).
Create a custom object that extends the AudioFile object and implements either Serializable or Parcelable - which can be tedious, but not impossible. With custom objects like this, the documentation may be lacking for this option.
Someone mentioned static as an option. This can generally work well, except you are talking about Android. Android can destroy and re-create the JVM for your app at any time when it is not visible to the user. So, if this AudioFile class is playing in the background in your app, strange behavior could occur if Android decides to kill the process.
You can use an object in the Application class, but is potentially has the same issues as #2.
Use SharedPreferences and some kind of index system to retrieve the file.
You can create a class of your own, which would accept an object of type AudioFile, and populate fields with its values.
public class MyAudioFile implements Parcelable{
private File file;
//other fields...
public MyAudioFile(AudioFile audioFile){
this.file = audioFile.getFile();
//populate other fields
}
//parcelable stuff
}
i'm developing an app that, when i press a button, downloads a XML file, put the xml data in a custom object and passes it to a second activity.
The problem is that something is wrong: when a call the startActivity() function the app crashes with a Runtime error.
My code is:
public void onClickBtn1(View view)
{
final ProgressDialog dlg = ProgressDialog.show( this, "Data wait", "Waiting data from the site ..");
// Thread to wait data
Thread th = new Thread() {
public void run() {
// Download and parse xml data
final DatiSport dati = new DatiSport();
boolean ret = dati.download();
dlg.dismiss();
// check result
if (ret==true)
{
// -- Ok
handlerUI.post( new Runnable() {
public void run() {
Intent intSec = new Intent(AICSActivity.this, SportActivity.class);
intSec.putExtra("datiSport", dati);
startActivity(intSec);
}
});
}
else
{
The app crashes on the startActivity() call. When i break on the startActivity() line i'm not able to look the variable called 'dati' and i guess this is not well defined.
If i substitute dati with 12345, there is not problem.
Which is the problem with dati ?
--- Changed here cause I'm not enabled to reply myself ---
Ok guys. Thanks for replies!
My guess is that i need to re-design the app data.
My first attempt was: download the XML text and accommodate the data into a (rather) complex object. This object contain a list of championships, each of them contains a list of categories, each of them contains a list of teams.
The problem is that, since the Serializable is not working, the implementation of Parcelable is too complex and it should generate almost the same data as the xml file.
I'm wondering if it should be easier passing directly the xml text to other activities (they have to show in turn the list of championships, then the categories of a selected championship, then the list of teams for a selected category...)
Any other idea?
Extract from this Answer :
Serializable is a standard Java interface. You simply mark a class Serializable by implenting the interface, and Java will automatically serialize it in certain situations.
Parcelable is an Android specific interface where you implement the serialization yourself. It was created to be far more efficient that Serializable, and to get around some problems with the default Java serialization scheme.
Extract from this answer :
Seeing Parcelable might have triggered the question, why is Android
not using the built-in Java serialization mechanism? It turns out that
the Android team came to the conclusion that the serialization in Java
is far too slow to satisfy Android’s interprocess-communication
requirements. So the team built the Parcelable solution. The
Parcelable approach requires that you explicitly serialize the members
of your class, but in the end, you get a much faster serialization of
your objects.
After seeing some answer on StackOverFlow, i come to conclusion that Parcelable is optimized than Serialization in android.
How to make class to Parcelable ?? (Check out this, this & this tutorials)
Use a Serializable or Parcelable when passing objects
You need a class to implement the Serializable class
//to pass :
intent.putExtra("MyClass", obj);
// to retrieve object in second Activity
getIntent().getSerializableExtra("MyClass");
Your class would look something like this;
import java.io.Serializable;
#SuppressWarnings("serial") //with this annotation we are going to hide compiler warning
public class MyClass implements Serializable {
public Deneme(Object obj){
this.obj= obj;
}
private Object obj;
}
The Intent class has a method as
putExtra(String name, int value)
thats why it works when you put 12345 at the place of "value", but there is no overloaded version of putExtra that takes "DatiSport" object.
You must ensure that "DatiSport" is Serializable or Parcelable.
See below for more info-
http://developer.android.com/reference/android/content/Intent.html#putExtra%28java.lang.String,%20java.io.Serializable%29
How to send an object from one Android Activity to another using Intents?
How to pass an object from one activity to another on Android
Make your class implement Serializable interface and then pass object instances in intent extra.
To pass data from one Activity to another :
intent.putExtra("ClassName", obj);
To retrieve data in the Second Activity from the First Activity :
getIntent().getSerializableExtra("ClassName");
I found the problem !!!
An internal class were not implementing Serializable!
In the dump window i saw the internal object 'ioe' that said that there was a NotSerializable error and the name of the class!!
Now i checked each internal class and the data is passed to the next activity.
Thanks a lot
To send intent with any object from one activity to another activity we can use Parcelable Interface or Serializable Interface. What is the difference between two? Which one is preferable to use?
Moreover, we can send string, integer type of object without using all this Interface. How it is possible?
Java Serializable:
Serializable comes from standard Java and is much easier to implement all you need to do is implement the Serializable interface and add override two methods.
private void writeObject(java.io.ObjectOutputStream out)
throws IOException
private void readObject(java.io.ObjectInputStream in)
throws IOException, ClassNotFoundException
The problem with Serializable is that it tries to appropriately handle everything under the sun and uses a lot reflection to make determine the types that are being serialized. So it becomes a beefy Object
Androids Parcelable:
Android Inter-Process Communication (AIPC) file to tell Android how is should marshal and unmarshal your object.It is less generic and doesn't use reflection so it should have much less overhead and be a lot faster.
You can send String, Integer and such data types, and also the objects of the classes that implemented Parcelable interface as follows...
Intent intent = new Intent(CallingActivity.this, CalledActivity.class);
intent.putExtra("IntegerExtra", intValue);
intent.putExtra("StringExtra", stringValue);
intent.putExtra("ParcExtra", parcObject);
startActivity(intent);
And, at the receiving end you can write the following code,
intValue = getIntent().getIntExtra("IntegerExtra", 0);
stringValue = getIntent().getStringExtra("StringExtra");
parcObject = ((YourParcalabeDataType) getIntent().getParcelableExtra("ParcExtra"));
Hope this may solve your problem...
:)
Intead of sending an object, it may be easier to just send a URI that points to your content. This would simplify the sending and would remove the need to send the object, since the URI would ideally point to the content you are interested in. Of course this depends on the content that you're trying to pass.
You can find difference between Parcelable and Serializable Interface from that link .
Basically Parcelable is created for android and is far more efficient than Serializable.
You can simply send string or integer by using Bundles and linking those bundles to intents.
Intent i = new Intent(getApplicationContext(),YourClass.class);
Bundle b = new Bundle();
b.putString("string", "string");
i.putExtras(b);
startActivity(i);