Can I send custom objects to Android Wear? - android

I am just learning how to develop for Android Wear, I have created a full screen Activity for Smart Watches and in my mobile part of the application I get some JSON data and create a list of custom objects from this.
On my mobile app I show the information for these objects in a ListView.
On the Wear piece of my application I want to show a limited version of this list, for example the top 3 from the list will be show on my full screen app on the Wearable.
My problem is that there doesn't seem to be a way to send Parcelable Objects to Android Wear, there is no option to putParcelable in the DataItem.
It looks like the only option is to send an object in bytes, like this:
public void sendWearableData(GoogleApiClient aGoogleApiClient, ArrayList<MyObject> myObjectList, String path)
{
googleApiClient = aGoogleApiClient;
byte[] testBytes = new byte[0];
if (googleApiClient.isConnected()) {
PutDataMapRequest dataMapRequest = PutDataMapRequest.create(path);
try {
testBytes = BytesUtils.toByteArray(myObjectList);
} catch (IOException e) {
e.printStackTrace();
}
dataMapRequest.getDataMap().putByteArray(Constants.TEST_KEY, testBytes);
PutDataRequest request = dataMapRequest.asPutDataRequest();
Wearable.DataApi.putDataItem(googleApiClient, request);
}
}
So I have to convert my object to bytes, send it to Android Wear and convert it back? This means that my Objects which I have implemented Parcelable on so I can send them via Intents now also need to implement Serializable, is this correct or is there a better way of doing it?

Bundle-like solution:
In my app I've created a lightweight class, specially for sending it from phone to watch. Because the code is shared between mobile and wearable parts of app it can be easily packed and restored on both devices without code duplication. It provides Bundle-like mechanism, but using DataMap.
Sample implementation:
public class MyObject {
public final long itemId;
public final long sortOrder;
public final String priceString;
public MyObject(long itemId, long sortOrder, String priceString) {
this.itemId = itemId;
this.sortOrder = sortOrder;
this.priceString = priceString;
}
public MyObject(DataMap map) {
this(map.getLong("itemId"),
map.getLong("sortOrder"),
map.getString("priceString")
);
}
public DataMap putToDataMap(DataMap map) {
map.putLong("itemId", itemId);
map.putLong("sortOrder", sortOrder);
map.putString("priceString", priceString);
return map;
}
}
Writing such class will let you consider the what actually needs to be send between devices to send as little as possible. It will also not break when any field will be added or removed (in oppose to the next solution).
Answering to your Parcelable concerns:
If you don't want to write the new class and want to reuse your existing code you can try to use the code below. It will let you stay with only Parcelable interface (without need to implement Serializable interface). I haven't tested it while sending across devices but it successes to marshall() and unmarshall() byte array to/from Parcel and store it in DataMap.
NOTE: I don't know exactly how Google Play Services hold all these DataApi data, but I'm afraid that something may break when such class will be updated.
For example the class will be updated on Android Wear, user will launch the app that would try to read the current data from DataApi (that was "serialized" using old version of this class) and try to read it from byte[] as if it was updated version. These concerns should be tested, but I don't think that they made DataApi so primitive "just because" or to make harder to develop apps on Wear.
I strongly recommend to use Bundle-like solution and to not use the Parcelable solution.
Use this at your own risk.
import android.os.Parcel;
import android.os.Parcelable;
import com.google.android.gms.wearable.DataMap;
/**
* <p>Allows to put and get {#link Parcelable} objects into {#link DataMap}</p>
* <b>USAGE:</b>
* <p>
* <b>Store object in DataMap:</b><br/>
* DataMapParcelableUtils.putParcelable(dataMap, "KEY", myParcelableObject);
* </p><p>
* <b>Restore object from DataMap:</b><br/>
* myParcelableObject = DataMapParcelableUtils.getParcelable(dataMap, "KEY", MyParcelableObject.CREATOR);
* </p>
* I do <b>not recommend</b> to use this method - it may fail when the class that implements {#link Parcelable} would be updated. Use it at your own risk.
* #author Maciej Ciemięga
*/
public class DataMapParcelableUtils {
public static void putParcelable(DataMap dataMap, String key, Parcelable parcelable) {
final Parcel parcel = Parcel.obtain();
parcelable.writeToParcel(parcel, 0);
parcel.setDataPosition(0);
dataMap.putByteArray(key, parcel.marshall());
parcel.recycle();
}
public static <T> T getParcelable(DataMap dataMap, String key, Parcelable.Creator<T> creator) {
final byte[] byteArray = dataMap.getByteArray(key);
final Parcel parcel = Parcel.obtain();
parcel.unmarshall(byteArray, 0, byteArray.length);
parcel.setDataPosition(0);
final T object = creator.createFromParcel(parcel);
parcel.recycle();
return object;
}
}
The code is also available on GitHub.

Related

unity android gameobject not working

So in my app i pass a game object, called datacontroller through out my three scenes. The persistent scene is an empty scene, the menuscreen scene and then the game scene. My application works perfectly on my computer and in editor mode but when i download the apk to my android tablet it no longer works! iv'e read this may have to do with my code for my object but i dont think i written anything that only works in the editor.
enter code here
using UnityEngine;
using UnityEngine.SceneManagement;
using System.Collections;
using System.IO; // The System.IO namespace contains functions related to loading and saving
files
public class DataController : MonoBehaviour
{
private RoundData[] allRoundData;
private PlayerProgress playerProgress;
private string gameDataFileName = "data.json";
void Start()
{
DontDestroyOnLoad(gameObject);
LoadGameData();
LoadPlayerProgress();
SceneManager.LoadScene("MenuScreen");
}
public RoundData GetCurrentRoundData()
{
// If we wanted to return different rounds, we could do that here
// We could store an int representing the current round index in PlayerProgress
return allRoundData[0];
}
public void SubmitNewPlayerScore(int newScore)
{
// If newScore is greater than playerProgress.highestScore, update playerProgress with the new value and call SavePlayerProgress()
if (newScore > playerProgress.highestScore)
{
playerProgress.highestScore = newScore;
SavePlayerProgress();
}
}
public int GetHighestPlayerScore()
{
return playerProgress.highestScore;
}
private void LoadGameData()
{
// Path.Combine combines strings into a file path
// Application.StreamingAssets points to Assets/StreamingAssets in the Editor, and the StreamingAssets folder in a build
string filePath = Path.Combine(Application.streamingAssetsPath, gameDataFileName);
if (File.Exists(filePath))
{
// Read the json from the file into a string
string dataAsJson = File.ReadAllText(filePath);
// Pass the json to JsonUtility, and tell it to create a GameData object from it
GameData loadedData = JsonUtility.FromJson<GameData>(dataAsJson);
// Retrieve the allRoundData property of loadedData
allRoundData = loadedData.allRoundData;
}
else
{
Debug.LogError("Cannot load game data!");
}
}
// This function could be extended easily to handle any additional data we wanted to store in our PlayerProgress object
private void LoadPlayerProgress()
{
// Create a new PlayerProgress object
playerProgress = new PlayerProgress();
// If PlayerPrefs contains a key called "highestScore", set the value of playerProgress.highestScore using the value associated with that key
if (PlayerPrefs.HasKey("highestScore"))
{
playerProgress.highestScore = PlayerPrefs.GetInt("highestScore");
}
}
// This function could be extended easily to handle any additional data we wanted to store in our PlayerProgress object
private void SavePlayerProgress()
{
// Save the value playerProgress.highestScore to PlayerPrefs, with a key of "highestScore"
PlayerPrefs.SetInt("highestScore", playerProgress.highestScore);
}
}
I am starting to go through tutorials of unity myself so I am not an expert. :)
But what I would try is the first thing. using System.IO; not sure if this will work to get files on android because android has a different file structure. So I would first remove it and sort of hard code the file path or comment out the code using System.IO classes then recompile apk in unity and check if it works. I also saw this post : http://answers.unity3d.com/questions/1023391/systemio-dont-work-on-android.html
If that did not work I would comment functionality out and compile apk and check if its working if its not comment more code out until you find the line or the code that causes it to error on android. This method takes long to troubleshoot or get your code causing the problem but for me this has worked before.
I am guessing as it is working on you pc its a class or something its referencing that's not available in android.
Please share your findings if you figure out what part of the code does it. As I would also want to know to prevent me from doing it. :)
Avoid the use of System.IO on Android and in general on Unity.
Use "Resources" instead, just following this steps:
Create a folder called "Resources"
Move json file on it and rename in .txt
Use this code to get the string:
var file = Resources.Load("filename_here") as TextAsset;
Debug.Log(file.text)

Trouble sending POST parameter with androidannotations

so I'm trying to send a simple String to my REST server from an Android app using androidannotations.
http://localhost:8080/TestServer_RESTJersey/api/lanceurs/parPays
Using Advanced REST client chrome extension, I send the parameter :
country=Europe
and it's working fine. Now my problem whith the Android app is that my request is received by the server, but the country parameter is always null. My others GET requests are all working perfectly.
Here is my RestClient class :
#Rest(converters = {MappingJacksonHttpMessageConverter.class, FormHttpMessageConverter.class})
public interface RestClient extends RestClientRootUrl, RestClientSupport{
#Get("/poke/simple")
public MessageResponse simplePoke();
#Get("/api/lanceurs/{name}")
public LaunchVehicleResponse nameRequest(String name);
//server doesn't get the parameter here...
#Post("/api/lanceurs/parPays")
public LaunchVehicleResponse countryRequest(String country);
}
Any help would be appreciated as usual, thanks!
EDIT :
server-side REST api :
#Path("api/lanceurs/parPays")
#POST
public String getLanceurByCountry(#FormParam("country") String country)
{
initData();
LaunchVehicleResponse lvr = new LaunchVehicleResponse();
ArrayList<LaunchVehicle> allv = myDatabase.getDataByCountry(country);
lvr.setData(allv);
return parseObjectToJson(lvr);
}
In JAX-RS, use #QueryParam annotation to inject URI query parameter into Java method. example,#QueryParam("country") String countryName,
Try the below, i guess, it should work
#Post("/api/lanceurs/parPays")
public LaunchVehicleResponse countryRequest(#QueryParam("country") String country);
Ok, it seems I figured out a way to get myself out of this mess.
I made a class LaunchVehicleRequest on my client, containing (among other things) a country String. When I need to send a request to my server, I instantiate this class and initialize LaunchVehicleRequest.country with the value I want (ex: "USA"). Then I send the whole object to my RestClient.
LaunchVehicleRequest lvreq = new LaunchVehicleRequest();
lvreq.setCountry("Europe");
LaunchVehicleResponse lvr = pm.countryRequest(lvreq);
...
#Rest(converters = {MappingJacksonHttpMessageConverter.class, FormHttpMessageConverter.class}, interceptors = { LoggingInterceptor.class } )
public interface RestClient extends RestClientRootUrl, RestClientSupport, RestClientHeaders{
#Post("/api/lanceurs/parPays")
public LaunchVehicleResponse countryRequest(LaunchVehicleRequest request);
}
I set up the same class on my server-side, which get the request as a string and then convert it in an object.
#Path("api/lanceurs/parPays")
#POST
public String getLanceurByCountry(String request)
{
// request={"country":"USA"}
//my json parsing function here
LaunchVehicleRequest lvreq = parseJsonToRequest(request);
...
}
I don't know is this is the best way, but hey it's working fine now and I'm using my LaunchVehicleRequest class for every different request I can need to, so it's not THAT bad I guess ^^'
Thanks everyone anyway ;)
As explained on the wiki, you can send form parameters this way:
#Rest(rootUrl = "http://company.com/ajax/services", converters = { FormHttpMessageConverter.class, MappingJackson2HttpMessageConverter.class })
public interface MyRestClient extends RestClientHeaders {
#RequiresHeader(HttpHeaders.CONTENT_TYPE)
#Post("/api/lanceurs/parPays")
public LaunchVehicleResponse countryRequest(MultiValueMap<String, Object> data);
}
MultiValueMap<String, Object> data = new LinkedMultiValueMap<>();
data.set("country, "Europe");
client.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE);
client.countryRequest(data);

Max size of string data that can be passed in intents [duplicate]

This question already has answers here:
Maximum length of Intent putExtra method? (Force close)
(9 answers)
Closed 2 years ago.
Is there a max limit to the string data that can be passed in intent extra? How much data can the below String str hold?
intentI1.putExtra("MyString", str);
My tests on Android API Level 24:
Intent intent = new Intent(MainActivity.this, DetailActivity.class);
// intent.putExtra("extra", new byte[1024 * 1024]); // 1024 KB = 1048576 B, android.os.TransactionTooLargeException
// intent.putExtra("extra", new byte[1024 * 512]); // 512 KB = 524288 B, android.os.TransactionTooLargeException
// intent.putExtra("extra", new byte[1024 * 506]); // 506 KB = 518144 B, android.os.TransactionTooLargeException
// intent.putExtra("extra", new byte[1024 * 505]); // 505 KB = 517120 B, android.os.TransactionTooLargeException
intent.putExtra("extra", new byte[1024 * 504]); // 504 KB = 516096 B, OK
startActivity(intent);
android.os.TransactionTooLargeException
https://developer.android.com/reference/android/os/TransactionTooLargeException.html
Because I need to send large amount of data to the activity, I am using the following solution (i know, its not perfect, but it can help):
public class ExtendedDataHolder {
private static ExtendedDataHolder ourInstance = new ExtendedDataHolder();
private final Map<String, Object> extras = new HashMap<>();
private ExtendedDataHolder() {
}
public static ExtendedDataHolder getInstance() {
return ourInstance;
}
public void putExtra(String name, Object object) {
extras.put(name, object);
}
public Object getExtra(String name) {
return extras.get(name);
}
public boolean hasExtra(String name) {
return extras.containsKey(name);
}
public void clear() {
extras.clear();
}
}
Then in MainActivity
ExtendedDataHolder extras = ExtendedDataHolder.getInstance();
extras.putExtra("extra", new byte[1024 * 1024]);
extras.putExtra("other", "hello world");
startActivity(new Intent(MainActivity.this, DetailActivity.class));
and in DetailActivity
ExtendedDataHolder extras = ExtendedDataHolder.getInstance();
if (extras.hasExtra("other")) {
String other = (String) extras.getExtra("other");
}
Checkout this post which says 1MB is a limit. Also checkout this one.
There is also a report on issues site.
The real question is: why pass a 1M data between activities? Perhaps a better way to achieve what you want is to persist this data and pass an identifier instead.
Because startActivity will finally pass the whole Intent data to ActivityManagerService through Binder. And the Binder transaction buffer has a limited fixed size, currently 1Mb. Google Ref
As per Android reference:
Sending data between activities
When sending data via an intent, you should be careful to limit the
data size to a few KB. Sending too much data can cause the system to
throw a TransactionTooLargeException exception.
Also its advisable to use Bundle class to set primitives known to the OS on Intent objects.
And to send composite or complex objects across activities. In such cases, the custom class should implement Parcelable.
Sending data between processes
The Binder transaction buffer has a limited fixed size, currently 1MB,
which is shared by all transactions in progress for the process. Since
this limit is at the process level rather than at the per activity
level, these transactions include all binder transactions in the app
such as onSaveInstanceState, startActivity and any interaction with
the system. When the size limit is exceeded, a
TransactionTooLargeException is thrown.
For the specific case of savedInstanceState, the amount of data should be kept small because the system process needs to hold on to the provided data for as long as the user can ever navigate back to that activity (even if the activity's process is killed). We recommend that you keep saved state to less than 50k of data.
Note: In Android 7.0 (API level 24) and higher, the system throws a TransactionTooLargeException as a runtime exception.

Using Square's Tape library how do i handle calling abstract classes

I'm using Square's Tape library and i've run in to a requirement where i basically need to have an abstract TapeTask class. The problem though is that the deserialization process for the GsonConverter (which implements the library's FileObjectQueue.Converter - as demonstrated in the sample project) doesn't play well with interfaces/abstract classes.
I thought it was a Gson deserialization problem so i registered my Gson instance with a custom TypeAdapter, but that still doesn't do the trick. I figure it has something to do with the FileObjectQueue.Converter.
I'm currently trying to work around this problem, with a nasty wrapper callback interface from my sub-tasks.
My requirement is to have a single TapeQueue and be able to send in multiple types of tasks. So I have a TapeTask abstract class and have concrete implementations like ImageDownloadTask, ImageUploadTask, UrlPostWithRetrofitTask, GoogleAnalyticsTrackerTask ... etc. all going in to a single queue.
Is there a way to achieve this. I guess my question boils down to:
What do i need to do to make the FileObjectQueue.Converter play well with abstract classes?
hint :P : The javadoc for that class says "..you need to also include the concrete class name in the serialized byte array" but i'm not sure what that means. If anyone could post an explanation of how the name can be included in the serialized byte array, in a way that achieves my purpose, i'd be grateful!
I went ahead and wrote an Abstract Gson Convertor. I don't think it's super-efficient but gets the job done:
/**
* Use GSON to serialize classes to a bytes.
*
* This variant of {#link GsonConverter} works with anything you throw at it.
* It is however important for Gson to be able to understand your inner complex objects/entities
* Use an {#link InterfaceAdapter} for these purposes.
*
*/
public class GsonAbstractClassConverter<T>
implements FileObjectQueue.Converter<T> {
public static final String CONCRETE_CLASS_NAME = "concrete_class_name";
public static final String CONCRETE_CLASS_OBJECT = "concrete_class_object";
private final Gson _gson;
public GsonAbstractClassConverter(Gson gson) {
_gson = gson;
}
#Override
public T from(byte[] bytes) {
Reader reader = new InputStreamReader(new ByteArrayInputStream(bytes));
JsonObject completeAbstractClassInfoAsJson = _gson.fromJson(reader, JsonObject.class);
Class<T> clazz;
try {
String className = completeAbstractClassInfoAsJson.get(CONCRETE_CLASS_NAME).getAsString();
clazz = (Class<T>) Class.forName(className);
} catch (ClassNotFoundException e) {
Timber.e(e, "Error while deserializing TapeTask to a concrete class");
return null;
}
String objectDataAsString = completeAbstractClassInfoAsJson.get(CONCRETE_CLASS_OBJECT)
.getAsString();
return _gson.fromJson(objectDataAsString, clazz);
}
#Override
public void toStream(T object, OutputStream bytes) throws IOException {
Writer writer = new OutputStreamWriter(bytes);
JsonObject completeAbstractClassInfoAsJson = new JsonObject();
completeAbstractClassInfoAsJson.addProperty(CONCRETE_CLASS_NAME, object.getClass().getName());
completeAbstractClassInfoAsJson.addProperty(CONCRETE_CLASS_OBJECT, _gson.toJson(object));
_gson.toJson(completeAbstractClassInfoAsJson, writer);
writer.close();
}
}

Approach to serialize cloud endpoint model class to Android file system

I have successfully created a cloud endpoint model that allows for easy retrieval of information from App Engine. To reduce the roundtrips and provide a faster user experience, I have identified one instance I wish to store to local storage.
Throughout the rest of my app, I am using ObjectInputStream to read and write the objects such as:
FileInputStream fis = context.openFileInput("PRIVFILE");
ObjectInputStream ois = new ObjectInputStream(fis);
AppModelState s = (AppModelState) ois.readObject();
This obviously requires all data members to implement the Serializable interface. The Model class extends GenericJSON and is not "Serializable", as
public final class ModelClass extends GenericJson {}
I could manually create a serializable object that maps to the model; however, that seems very amateur due to the number of attributes.
The other alternative I considered was creating a Serializable Object wrapper that simply has the JSON string as a member and provides a setter/getter accepting the ModelClass as parameters, such as:
class AppModelState implements Serializable {
private String modelClassJSON;
public ModelClass getModelClass() {
// generate a new ModelClass from the JSON
}
public void setModelClass(ModelClass c) {
// extract the JSON for storage
}
.....
}
I feel like there must be a better way and this should have been solved a dozen times but I am not finding any resources. Please provide input.
I'm doing exactly the same as you say in your question.
Since Cloud Endpoints objects are already serialized for transmit over the wire, they are also serializable to be stored locally. As an added bonus, with Android 3.0 or later, you don't even need to import any libraries -- it's already there! For example:
import com.google.api.client.extensions.android.json.AndroidJsonFactory;
import com.google.api.client.json.GenericJson;
import com.google.api.client.json.JsonFactory;
private static final JsonFactory JSON_FACTORY = new AndroidJsonFactory();
public void putObject(String key, Object value) throws Exception {
byte[] outputbytes = null;
if (value instanceof GenericJson) {
outputbytes = JSON_FACTORY.toByteArray(value);
} else {
ByteArrayOutputStream output = new ByteArrayOutputStream();
ObjectOutputStream objectstream = new ObjectOutputStream(output);
objectstream.writeObject(value);
objectstream.close();
outputbytes = output.toByteArray();
}
// persist "outputbytes" ...
}
public <T> T getObject(String key, Class<T> outclass) throws Exception {
// retrieve saved bytes...
byte[] valuebytes = ...
if (valuebytes[0] == '{' && valuebytes[1] == '"' && valuebytes[valuebytes.length-1] == '}') {
// Looks like JSON...
return JSON_FACTORY.fromString(new String(valuebytes, "UTF-8"), outclass);
} else {
ByteArrayInputStream input = new ByteArrayInputStream(valuebytes);
ObjectInputStream objectstream = new ObjectInputStream(input);
Object value = objectstream.readObject();
objectstream.close();
return outclass.cast(value);
}
}
Note that the default AndroidJsonFactory (as of Android v4.3, anyway) is quite slow when serializing long strings. Create a new JacksonFactory instead if you have performance problems. Everything else stays the same.
Update: If you want to serialize a list of GenericJson objects, you just have to create a GenericJson object that includes a list of those objects. For example:
import com.google.api.client.json.GenericJson;
import com.google.api.client.util.Key;
public static class PersistantJson extends GenericJson {
#Key public int one;
#Key public String two;
}
public static class PersistantJsonList extends GenericJson {
#Key public List<PersistantJson> list = new ArrayList<PersistantJson>();
}
You can now add all your PersistantJson (i.e. some class created by "generate cloud endpoint client library") objects to the .list element of a PersistantJsonList variable and then pass that variable to putObject(). Note that this requires all objects in the list to be of the same class so that deserialization knows what the type is (because JSON serialization does not record the type). If you use List<Object> then what is read back is a List<Map<String, Object>> and you have to extract the fields manually.
I think that doing standard Java serialization of classes that will be used with Endpoints doesn't work very well. The problem is that serialization is binary, and HTTP comm is string.
If you were doing the HTTP comm yourself, rather then using endpoints, I think you would have the same problem. In order to send the object you would serialize it (converting an string members to binary) and then you would have to convert the binary back to string.
So, if the amount of data you are using is not too much, it would probably be easiest to store your objects as JSON.

Categories

Resources