Has there anyone integrated ODOO with Android? - android

I am currently developing an android application for a client who is insisting to use Odoo for API. I don't have any idea about it. I am not getting it even after referring to this link. They provide an URL, Database name, username, and password. If anyone did Odoo with Android before, can you give any suggestions?

There are a lot of ways to connect Android to Odoo. Here they are:
Json-RPC
XML-RPC (especially aXMLRPC, this is what I am using)
There is also a framework called Odoo Mobile Framework . I have tried it but found a lot of issues and I was not able to get it work properly. You can find the documentation here.
Odoo has a Web Service API which is available for Python, Ruby, PHP and Java. I strongly recommend to take a look.
For my case, I have cloned the aXMLRPC git repository, created a package in my project and adapted the original package name. But recently I have found this on Stack Overflow explaining how to add aXMLRPC to your Android project using Gradle (I didn't give it a try yet).
Odoo had made available three endpoints:
xmlrpc/2/db to get the list of available databases on your server, it does not require to be authenticated;
xmlrpc/2/common to log in to the server, it does not require to be authenticated;
xmlrpc/2/object, is used to call methods of odoo models via the execute_kw RPC function.
public class OdooConnect {
String url;
private XMLRPCClient client;
public OdooConnect(String serverAddress, String path) {
url = serverAddress + "/xmlrpc/2/" + path;
client = new XMLRPCClient(url);
}
public Object login(String db, String username, String password) {
Object object;
try {
object = client.call("login", db, username, password);
return object;
} catch (XMLRPCException e) {
e.printStackTrace();
}
return null;
}
public Object checkServer() {
Object object;
try {
object = client.call("list", new Object[]{});
return object;
} catch (XMLRPCException e) {
e.printStackTrace();
}
return null;
}
}
In this class, the constructor as arguments the server address (it can be http(s)://your_ip_address:the_port_number) and the path ('db', 'common' or 'object').
The checkServer method returns an object which is actually an array containing the list of available databases.
The login mehtod returns an Integer which is the Id of the authenticated user.
For the Odoo CRUD mehtods (search_read, search_count, search, write, create, unlink) you can take a look to the Odoo Web Service API Java code matching the method you want.
Here is an example of the search_read method. I assume that you've an XMLRPCClient named client.
public Object search_read(String db, int user_id, String password, String object, List conditions, Map<String, List> fields) {
Object result = null;
try {
result = client.call("execute_kw", db, user_id, password, object, "search_read", conditions, fields);
} catch (XMLRPCException e) {
e.printStackTrace();
}
return result;
}
Where
object is an Odoo model for example "res.partner"
conditions is the domain (filter) something like this: Collections.singletonList(Collections.singletonList(Arrays.asList("supplier", "=", true)));
fields, the fields you want to get,
fields = new HashMap() {{put("fields", Arrays.asList("id","name","is_company","street")); }};
You must cast the result of the method to Object[] which will give you an array containing a list of objects each representing a record.
Object[] objects = (Object[]) result;
if (objects.length > 0) {
for (Object object : objects) {
String name= OdooUtil.getString((Map<String, Object>) object, "name");
boolean is_company= OdooUtil.getBoolean((Map<String, Object>) object, "is_company");
String street = OdooUtil.getString((Map<String, Object>) object, "street");
int id= OdooUtil.getInteger((Map<String, Object>) object, "id");
}
}
Here the OdooUtil class
public class OdooUtil {
public static String getString(Map<String, Object> map, String fieldName) {
String res = "";
if (map.get(fieldName) instanceof String) {
res = (String) map.get(fieldName);
}
return res;
}
public static Integer getInteger(Map<String, Object> map, String fieldName) {
Integer res = 0;
if (map.get(fieldName) instanceof Integer) {
res = (Integer) map.get(fieldName);
}
return res;
}
public static Double getDouble(Map<String, Object> map, String fieldName) {
Double res = 0.0;
if (map.get(fieldName) instanceof Double) {
res = (Double) map.get(fieldName);
}
return res;
}
public static Boolean getBoolean(Map<String, Object> map, String fieldName) {
Boolean res = false;
if (map.get(fieldName) instanceof Boolean) {
res = (Boolean) map.get(fieldName);
}
return res;
}
public static Float getFloat(Map<String, Object> map, String fieldName) {
Float res = 0f;
if (map.get(fieldName) instanceof Float) {
res = (Float) map.get(fieldName);
}
return res;
}
}
If you have a many2one field you only have access to the id and the name of the related record. You can use the following class to get the id and the name of the many2one record.
public class Many2One {
private int id;
private String name;
public Many2One() {
}
public static Many2One getMany2One(Map<String, Object> stringObjectMap, String fieldName) {
Integer fieldId = 0;
String fieldValue = "";
Many2One res = new Many2One();
if (stringObjectMap.get(fieldName) instanceof Object[]) {
Object[] field = (Object[]) stringObjectMap.get(fieldName);
if (field.length > 0) {
fieldId = (Integer) field[0];
fieldValue = (String) field[1];
}
}
res.id = fieldId;
res.name = fieldValue;
return res;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
}
Example of usage of Many2One class
String partner_name= Many2One.getMany2One((Map<String, Object>) object, "partner_id").getName();
int partner_id= Many2One.getMany2One((Map<String, Object>) object, "partner_id").getId();
For other remaining CRUD methods, you can easily find a way how they work by reading the Odoo Web Service API documentation.
I hope this gives you some insights.

This is Just an Example did to access contacts/partners from odoo:
#!/usr/bin/env python
import csv
from xmlrpclib import ServerProxy
SERVER = 'http://localhost:8069'
DATABASE = 'testcompany'
USERNAME = 'admin'
PASSWORD = 'password'
FILE_PATH = 'ODOO_clientsMain2_test.csv'
server = ServerProxy('http://localhost:8069/xmlrpc/common')
user_id = server.login(DATABASE, USERNAME, PASSWORD)
server = ServerProxy('http://localhost:8069/xmlrpc/object')
def search(list, key):
for item in list:
return item[key]
reader = csv.reader(open(FILE_PATH,'rb'))
for row in reader:
#print row
partner_template = {
'name': row[0],
#'company_id': row[1],
}
if row[2] is not None and row[2]<>'':
partner_template.update({'email': row[2]})
if row[5] is not None and row[5]<>'':
partner_template.update({'tin': row[5]})
if row[6] is not None and row[6]<>'':
partner_template.update({'ref': row[6]})
if row[8] is not None and row[8]<>'':
partner_template.update({'phone': row[8]})
if row[9] is not None and row[9]<>'':
partner_template.update({'mobile': row[9]})
print partner_template
partner_id = server.execute_kw(DATABASE, user_id, PASSWORD, 'res.partner', 'create', [partner_template])
#create External ID
external_ids = {
'model': 'res.partner',
'name': row[11],
'res_id': partner_id,
}
external_id = server.execute_kw(DATABASE, user_id, PASSWORD, 'ir.model.data', 'create', [external_ids])
# update related fields
if row[7] is not None and row[7]<>'':
#look up and update payment term
payment_term_id = server.execute_kw(DATABASE, user_id, PASSWORD, 'account.payment.term', 'search_read', [[['name','=',row[7]],['active', '=', True]]],{'fields': ['id'], 'limit': 1})
if payment_term_id is not None:
id = server.execute_kw(DATABASE, user_id, PASSWORD, 'res.partner', 'write', [[partner_id],{'property_payment_term': search(payment_term_id,'id')}])
if row[10] is not None and row[10]<>'':
#look up and update pricelist
pricelist_id = server.execute_kw(DATABASE, user_id, PASSWORD, 'product.pricelist', 'search_read', [[['name','=',row[10]],['active', '=', True]]],{'fields': ['id'], 'limit': 1})
if pricelist_id is not None:
id = server.execute_kw(DATABASE, user_id, PASSWORD, 'res.partner', 'write', [[partner_id],{'property_product_pricelist': search(pricelist_id,'id')}])

If you are creating your application from stretch and only required Android API for Odoo, here is open-source API https://github.com/oogbox/odoo-mobile-api (Odoo android api)
To use in android, first add the following dependency to your app level build.gradle
compile 'com.oogbox.api:odoo:1.0.0'
Documentation: https://github.com/oogbox/odoo-mobile-api#getting-started
Thanks

Related

Firebase: How to ignore validate type of fields for an object model?

My android application is crashing on start up, because of a small error in my back-end side which is in firebase. A document in my firestore database has a field in which an Image object (id and url) is expected but for some reason it's stored as a long in the database.
I get this error:
java.lang.RuntimeException: Could not deserialize object. Can't convert object of type java.lang.Long to type com..data.entity.gps.tracker.ImageEntity
My ImageEntity class:
public class ImageEntity {
private int id;
private String image_id;
public String getImage_id() {
return image_id;
}
public void setImage_id(String image_id) {
this.image_id = image_id;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
I can't just exclude the cover ImageEntity object, it's an integral part of my app, how can I add a validation step to check if the cover object is a valid object and not a long?
This is where the crash happens:
public List<Model> mapList(QuerySnapshot querySnapshot) {
List<Model> list = new ArrayList<>();
for (QueryDocumentSnapshot queryDocumentSnapshot : querySnapshot) {
Model model = map(queryDocumentSnapshot);
long id = ((Release) model).getId();
if (!addedReleasesIDs.contains(id)) {
list.add(model);
addedReleasesIDs.add(id);
}
}
return list;
}
public Model map(QueryDocumentSnapshot queryDocumentSnapshot) {
// Auto serialization
Entity entity = queryDocumentSnapshot.toObject(getEntityClass()); // CRASH HERE
return map(entity);
}
If you need to take control over the deserializaition of a document snapshot, you should access its fields as a Map<String, Object> using getData() instead of using the Firebase SDKs internal mapper.
Map<String, Object> data = (Map<String, Object>) queryDocumentSnapshot.getData();
With this, you can now get() the individual fields by name out of the data map, check their types, and convert them to whatever other type you want for your model object.
This is also faster in terms of performance than using toObject because it doesn't have to use Java reflection.

Realm: updateOrInsert without index

I have a RealmObject, which is used as a temporary data cache only (there will be many entries). I also wrote a static method add() so I can easily add a new entry, but it seems too complicated. Here is the whole class:
public class ExchangePairPriceCache extends RealmObject {
#Index
private String exchangeName;
#Index
private String baseCurrency;
#Index
private String quoteCurrency;
private float price;
private long lastPriceUpdate;
public ExchangePairPriceCache() {
exchangeName = "";
baseCurrency = "";
quoteCurrency = "";
price = 0;
lastPriceUpdate = 0;
}
public ExchangePairPriceCache(String exchangeName, String baseCurrency, String quoteCurrency) {
this.exchangeName = exchangeName;
this.baseCurrency = baseCurrency;
this.quoteCurrency = quoteCurrency;
price = 0;
lastPriceUpdate = 0;
}
public void setPrice(float price) {
// this needs to be called inside a Realm transaction if it's a managed object
this.price = price;
lastPriceUpdate = System.currentTimeMillis();
}
public float getPrice() {
return price;
}
/* static functions */
public static void add(String exchangeName, String baseCurrency, String quoteCurrency, float price) {
Realm realm = Realm.getDefaultInstance();
realm.executeTransaction(r -> {
ExchangePairPriceCache priceCache = r.where(ExchangePairPriceCache.class)
.equalTo("exchangeName", exchangeName)
.equalTo("baseCurrency", baseCurrency)
.equalTo("quoteCurrency", quoteCurrency).findFirst();
if(priceCache != null) {
priceCache.setPrice(price);
} else {
priceCache = new ExchangePairPriceCache(exchangeName, baseCurrency, quoteCurrency);
priceCache.setPrice(price);
ExchangePairPriceCache finalPriceCache = priceCache;
r.insert(finalPriceCache);
}
});
realm.close();
}
public static ExchangePairPriceCache get(String exchangeName, String baseCurrency, String quoteCurrency) {
Realm realm = Realm.getDefaultInstance();
ExchangePairPriceCache priceCache = realm.where(ExchangePairPriceCache.class)
.equalTo("exchangeName", exchangeName)
.equalTo("baseCurrency", baseCurrency)
.equalTo("quoteCurrency", quoteCurrency)
.greaterThan("lastPriceUpdate", System.currentTimeMillis() - TimeUnit.SECONDS.toMillis(10)).findFirst();
if(priceCache != null)
priceCache = realm.copyFromRealm(priceCache);
realm.close();
return priceCache;
}
public static void deleteAll() {
Realm realm = Realm.getDefaultInstance();
realm.executeTransaction(r -> r.delete(ExchangePairPriceCache.class));
realm.close();
}
}
Questions:
Is this a good design (having static functions for ease of use)? I like how I can insert new entries into cache like ExchangePairPriceCache.add("NASDAQ", "AAPL", "USD", 100.5); and delete all with ExchangePairPriceCache.deleteAll() when needed.
How can I simplify add() function? Right now I check if entry already exists and then update the price and if it doesn't, I create a new object and insert it into Realm. I am not able to use updateOrInsert because I don't have unique index for object.
Maybe I am just questioning myself too much and this is all good as it is. But I'd really appreciate some input from experts who use it daily.
You should use a "Repository design pattern" with a DAO object (Data Access Object), to do all your read/ write transactions in realm.
Model class should be a blind copy of objects just holding entities.
Since you do not have any unique identifiers, you can try below
Cache the Exchange pair in Shared preferences file (if they are added earlier or not)
For faster read/writes : Create a temporary unique identifier with a combination of key-value pair that you already have
eg : (exchangeName + baseCurrency + quoteCurrency) - Cast into proper formats to create some unique key with all these values.

Deserialize json with same key but different type in android using Jackson

I am calling web-services which can have 2 types of json object in response. Now sometimes i get key profile with type String and sometimes it may have same key with type 'ProfileSubObject'. So how to manage this case? Below are my two types of object. I am using Jackson library to parse json.
1.)
{
"data": [
{
"profession": "iOS Developer",
"thanks": {
"count": 5
},
"profile": "test"
}
]
}
2.)
{
"data": [
{
"profession": "iOS Developer",
"thanks": {
"count": 5
},
"profile": {
"val1":"test1",
"val2":"test2"
}
}
]
}
Key profile have 2 different type of object based on web-service call.
Following is my data class structure.
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonIgnoreProperties(ignoreUnknown = true)
public class DataObject {
#JsonProperty("profession")
private String profession;
#JsonProperty("profile")
private ProfileObject profile;
#JsonProperty("thanks")
private ThanksObject thanks;
public String getProfession() {
return profession;
}
public ThanksObject getThanks() {
return thanks;
}
public ProfileObject getProfile() {
return profile;
}
}
And Profile class is as per below.
public class ProfileObject {
ProfileObject(){
}
ProfileObject(ProfileSubObject profileSubObject){
this.profileSubObject= profileSubObject;
}
ProfileObject(String profile){
this.profile= profile;
}
private ProfileSubObject profileSubObject;
private String profile;
public ProfileSubObject getProfileSubObject() {
return profileSubObject;
}
}
Now when i parse my object, ProfileObject is always null. I want it to get parsed based on proifle key data type.
Anyone could help me with parsing?
In constructing the solution, I faced two problems:
the Json structure does not match a single DataObject
the original problem of deserializing same property into differnt types of Java objects.
The first problem I solved by constructing JavaType objects which tell Jackson the generic type of the collections involved. There are two such collections: a Map, consisting of a single entry with key "data" and value of List of DataObjects
The second problem, I solved with the Jackson feature of #JsonAnySetter which directs Jackson to call a single method for all properties it doesn't recognize. For this purpose, I added #JsonIgnore to the profile variable to make sure that Jackson indeed doesn't recognize it. Now Jackson calls the same method for the two input jsons
This is the new DataObject class:
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonIgnoreProperties(ignoreUnknown = true)
public class DataObject
{
#JsonProperty("profession")
public String profession;
#JsonIgnore // forcing jackson to not recognize this property
public ProfileObject profile;
#JsonProperty("thanks")
public ThanksObject thanks;
public String getProfession() { return profession; }
public void setProfession(String p) { profession = p; }
public ThanksObject getThanks() { return thanks; }
public void setThanks(ThanksObject t) { thanks = t; }
public ProfileObject getProfile() { return profile; }
public void setProfile(ProfileObject p) { profile = p; }
#JsonAnySetter
public void setProfileFromJson(String name, Object value)
{
// if value is single String, call appropriate ctor
if (value instanceof String) {
profile = new ProfileObject((String)value);
}
// if value is map, it must contain 'val1', 'val2' entries
if (value instanceof Map) {
ProfileSubObject profileSubObject =
new ProfileSubObject(((Map<String, String>)value).get("val1"), ((Map<String, String>)value).get("val2"));
profile = new ProfileObject(profileSubObject);
}
// error?
}
}
Here is my test method, which includes the java type construction I mentioned:
public static void main(String[] args)
{
try (Reader reader = new FileReader("C://Temp/xx2.json")) {
ObjectMapper mapper = new ObjectMapper();
// type of key of map is String
JavaType stringType = TypeFactory.defaultInstance().constructType(String.class);
// type of value of map is list of DataObjects
JavaType listOfDataObject = TypeFactory.defaultInstance().constructCollectionType(List.class, DataObject.class);
// finally, construct map type with key and value types
JavaType rootMap = TypeFactory.defaultInstance().constructMapType(HashMap.class, stringType, listOfDataObject);
Map<String ,List<DataObject>> m = mapper.readValue(reader, rootMap);
DataObject do1 = m.values()
// get first (only?) value in map (it is list)
.stream().findFirst().orElse(Collections.emptyList())
// get first (only?) item in list - it is the DataObject
.stream().findFirst().orElse(null);
System.out.println(do1.profile);
System.out.println(do1.profile.profile);
System.out.println(do1.profile.profileSubObject.val1 + " " + do1.profile.profileSubObject.val2);
} catch (Exception e) {
e.printStackTrace();
}
}
This may be of help in regards to parsing JSON, use a JsonReader. It does assume you are using RESTful webservice and have already gotten a HttpURLConnection and an InputStream from the connection.
https://developer.android.com/reference/android/util/JsonReader.html

How to get timestamp(rowversion) from sql azure to android with mobileservice

i have a problem getting timestamp(rowversion) from my SQL Azure database.
In my tables there is a column with datatype timestamp. This timestamp isn't similar to datetime, it's more like a rowversion.
I can get all other data in this table with the query from MobileServiceTable, there is no problem.
But this special datatype is a problem.
My class for this table looks like:
public class ArbeitsgangBezeichnung {
#com.google.gson.annotations.SerializedName("id")
private int ID;
#com.google.gson.annotations.SerializedName("ABZ_ArbeitsgangBezeichnungID")
private int ABZ_ArbeitsgangBezeichnungID;
#com.google.gson.annotations.SerializedName("ABZ_Bezeichnung")
private String ABZ_Bezeichnung;
#com.google.gson.annotations.SerializedName("ABZ_RowVersion")
private StringMap<Number> ABZ_RowVersion;
//constructor, getter, setter, etc....
}
If i login in Azure and look at the table, there are my example values and the automatic generated timestamp. The timestamp value looks like "AAAAAAAAB/M=". If i login in sql database and let me show the data, then for timestamp there is only "binarydata" (in pointed brackets) and not that value as it is shown in Azure.
The variable "ABZ_RowVersion" should include this timestamp, but the data in the StringMap doesn't look like the one in Azure. I tried String and Byte as datatype for the StringMap, but it doesn't helped.
I tried byte[] for ABZ_RowVersion, but then i got an exception in the callback method.
Then i tried Object for ABZ_RowVersion, that time i found out, that it is a StringMap, but nothing more.
Does anybody know, how to get the data from timestamp, i need it for comparison.
Thanks already
When you create a timestamp column in a table, it's essentially a varbinary(8) column. In the node SQL driver, it's mapped to a Buffer type (the usual node.js type used for binary data). The object which you see ({"0":0, "1":0, ..., "length":8}) is the way that a buffer is stringified into JSON. That representation doesn't map to the default byte array representation from the Gson serializer in Android (or to the byte[] in the managed code).
To be able to use timestamp columns, the first thing you need to do is to "teach" the serializer how to understand the format of the column returned by the server. You can do that with a JsonDeserializer<byte[]> class:
public class ByteArrayFromNodeBufferGsonSerializer
implements JsonDeserializer<byte[]> {
#Override
public byte[] deserialize(JsonElement element, Type type,
JsonDeserializationContext context) throws JsonParseException {
if (element == null || element.isJsonNull()) {
return null;
} else {
JsonObject jo = element.getAsJsonObject();
int len = jo.get("length").getAsInt();
byte[] result = new byte[len];
for (int i = 0; i < len; i++) {
String key = Integer.toString(i);
result[i] = jo.get(key).getAsByte();
}
return result;
}
}
}
Now you should be able to read data. There's still another problem, though. On insert and update operations, the value of the column is sent by the client, and SQL doesn't let you set them in them. So let's take this class:
public class Test {
#SerializedName("id")
private int mId;
#SerializedName("name")
private String mName;
#SerializedName("version")
private byte[] mVersion;
public int getId() { return mId; }
public void setId(int id) { this.mId = id; }
public String getName() { return mName; }
public void setName(String name) { this.mName = name; }
public byte[] getVersion() { return mVersion; }
public void setVersion(byte[] version) { this.mVersion = version; }
}
On the insert and update operations, the first thing we need to do in the server-side script is to remove that property from the object. And there's another issue: after the insert is done, the runtime doesn't return the rowversion property (i.e., it doesn't update the item variable. So we need to perform a lookup against the DB to retrieve that column as well:
function insert(item, user, request) {
delete item.version;
request.execute({
success: function() {
tables.current.lookup(item.id, {
success: function(inserted) {
request.respond(201, inserted);
}
});
}
});
}
And the same on update:
function update(item, user, request) {
delete item.version;
request.execute({
success: function() {
tables.current.lookup(item.id, {
success: function(updated) {
request.respond(200, updated);
}
});
}
});
}
Now, this definitely is a lot of work - the support for this type of column should be better. I've created a feature request in the UserVoice page at http://mobileservices.uservoice.com/forums/182281-feature-requests/suggestions/4670504-better-support-for-timestamp-columns, so feel free to vote it up to help the team prioritize it.

How to get single string value from Json object Android?

I have this Json statement :
{name=Adam Schmidt, id=43}
and I want to extract the value of the name,
trying this code but it didn't work
// parse json data
try {
JSONObject userObject = new JSONObject(result);
userName = userObject.getString("name");
tvName.setText(userName);
} catch (Exception ex) {
ex.printStackTrace();
}
You statement is not valid json. Here's the sample of valid json.
{\"name\":\"Adam Schmidt\", \"id\":43}
Update:
For number value, no quotation mark
Use GSON. Super easy.
Declare a class that represents your JSON structure:
public class Person {
private String name;
private int id;
public String getName() {
return name;
}
public int getId() {
return id;
}
}
Then you can do:
String json = "{\"name\":\"Adam Schmidt\", \"id\":43}";
String userName = new Gson().fromJson(json, Person.class).getName();
tvName.setText(userName);
This is much better than haphazardly parsing chunks with various get methods all over your code. Plus you get a nice object to pass around and use in your object oriented code.

Categories

Resources