I've been able to hackily implement the DataDroid library from Google's IO presentation in 2010 into my Android project, and it works great when I have a 1-1 Activity - Request relationship. However, in my FilterSelectActivity I need to make multiple calls to fill the drop-down boxes on my view so the user can select filters. The problem is that the way the library is structured it is not immediately obvious how to make multiple (distinct) calls to the WS using the existing library/callbacks. In particular, I can setup my own callThisMethodWS functions, but there's only one onRequestFinished signature:
public void onRequestFinished(final int requestId, final int resultCode, final Bundle payload)
Now, the requestId is a pseudorandom int that is generated by the specific calling function, so it indicates a "unique" request in terms of its parameters but certainly not a specific request type. resultCode is of no use and I'm not sure what I could do with Bundle other than maybe grabbing an intent extra.
If anyone has implemented DataDroid in their project with multiple WS calls in one activity I would like to know how you differentiated the requests so you could fill your various ArrayLists or ArrayAdapters.
I'm the developer of DataDroid and I've since then released version 2 of datadroid with allows much more easily to send multiple requests from a single Activity or Fragment.
I've also added a new sample (DoubleListActivity in the sample project) which calls 2 webservices from the same activity.
I was able to accomplish this by setting up some constants in the class to identify request types, create a request type member, set that member to the specific constant in each respective WS call, then use that request type member in a conditional in the processing of the result.
In my app I needed something like this too. I have a lot of activities than can have multiple calls to WS, some have more than 5 or 6 differents calls to handle.
To implement this behavior, I added a Request object that contains the request type and the default onRequestFinished callback, that will call OnRequestSuccess, OnRequestError... functions. Then I have an Activity with a SparseArray of Requests, that implements OnRequestFinishedListener and redirects everything to the correct Request. All my activities with WS calls inherit that Activity. I recently implemented this for fragments too.
Related
I have been reading the Facebook documentation. The Facebook documentation for asking/sending gifts mentioned a YOUR_OBJECT_IDfor this call:
FB.ui({method: 'apprequests',
message: 'Take this bomb and blast your way to victory!',
to: 'RECIPIENT_USER_ID'
action_type:'send',`enter code here`
object_id: 'YOUR_OBJECT_ID', // Where do I get this ?
data: 'Friend Smash Custom Tracking 1'
}, function(response) {
console.log(response);
});
How do I get get it? I have already created my object inside Open Graph, but there is no object id specified. Do I need to initiate a create request for the user from my app for that object or how is this suppose to work?
The request_object_id is what you will get back from the dialog, after the user sent the gift – it is part of the dialog’s return value. It is simply the identifier for the request that was send.
https://developers.facebook.com/docs/games/requests/v2.1#response
You can use it to read details of the request back from the API.
OK, so since this is about the object id that one can pass to the dialog:
That documentation section already links to https://developers.facebook.com/docs/sharing/opengraph/custom, where this is explained in more detail.
Basically, you need to set up your own Open Graph action and Open Graph object. This will define what your object is, and what players of the game can do with it.
Open Graph objects can be created in two ways:
You can host them yourself. To do this, you simply provide URLs to HTML pages which include the Open Graph meta data that specifies the object property values. Facebook will then read the meta data from those URLs (“scraping”). In that case, you would simply pass the object URL to the dialog as object_id.
You can use the Object API to create objects that Facebook will host for you. These can either be “app-owned” or “user-owned” – depends on if the are specific to a certain user, or “common” objects to be used by all users of your app. Creating an object via that API will give you back an object id, that you can then pass to the dialog.
If you are not familiar with the whole Open Graph Story concept yet, then I recommend you start by having a look at the whole Open Graph section, https://developers.facebook.com/docs/sharing/opengraph
Assume that you have a cross platform application. The application runs on Android and on iOS. Your shared language across both platforms is Java. Typically you would write your business logic in Java and all you UI specific part in Java (for Android) and Objective-C (for iOS).
Typically when you implement the MVP pattern in a cross platform, cross language application you would have the Model and the Presenter in Java and provide a Java interface for your Views which is known to your presenters. This way your shared Java presenters can communicate with whatever view implementation you use on the platform specific part.
Lets assume we want to write a iOS app with a Java part which could be shared later on with the same Android app. Here is a graphical representation of the design:
On the left side there is the Java part. In Java you write your models, controllers as well as your view interfaces. You make all the wiring using dependency injection. Then the Java code can be translated to Objective-C using J2objc.
On the right side you have the Objective-C part. Here your UIViewController's can implement the Java interfaces which where translated into ObjectiveC protocols.
Problem:
What I am struggling about is how navigation between views takes place. Assume you are on UIViewControllerA and you tap a button which should bring you to UIViewControllerB. What would you do?
Case 1:
You report the button tap to the Java ControllerA (1) of UIViewControllerA and the Java ControllerA calls Java ControllerB (2) which is linked to UIViewControllerB (3). Then you have the problem that you do not know from the Java Controller side how to insert the UIViewControllerB in the Objective-C View hierarchy. You cannot handle that from the Java side because you have only access to the View interfaces.
Case 2:
You can make the transition to UIViewControllerB whether it is modal or with a UINavigationController or whatever (1). Then, first you need the correct instance of UIViewControllerB which is bind to the Java ControllerB (2). Otherwise the UIViewControllerB could not interact which the Java ControllerB (2,3). When you have the correct instance you need to tell Java ControllerB that the View (UIViewControllerB) has been revealed.
I am still struggling with this problem of how to handle the navigation between different controllers.
How can I model the navigation between different Controllers and handle the cross platform View changes appropriately?
Short answer:
Here is how we do it:
For simple "normal" stuff (like a button that opens the device camera or opens another Activity/UIViewController without any logic behind the action) - ActivityA directly opens ActivityB. ActivityB is now responsible communicating with the app shared logic layer if needed.
For anything more complex or logic dependent we're using 2 options:
ActivityA calls a method of some UseCase which returns an enum or public static final int and takes some action accordingly -OR-
Said UseCase can call a method of a ScreenHandler we registered earlier which knows how to open common Activities from anywhere in the app with some supplied parameters.
Long answer:
I'm the lead developer in a company using a java library for the application's models, logic, and business rules which both mobile platforms (Android and iOS) implement using j2objc.
My design principles come directly from Uncle Bob and SOLID, I really dislike the usage of MVP or MVC when designing whole applications complete with inter-component communications because then you start linking each Activity with 1 and only 1 Controller which sometimes is OK but most of the times you end up with a God Object of a controller that tends to change as much as a View. This can lead to serious code smells.
My favorite way (and the one I find cleanest) of handling this is breaking everything up into UseCases each of which handles 1 "situation" in the app. Sure you can have a Controller that handles several of those UseCases but then all it knows is how to delegate to those UseCases and nothing more.
Additionally, I don't see a reason of linking every action of an Activity to a Controller sitting in the logical layer, if this action is a simple "take me to the map screen" or anything of this sort. The role of the Activity should be handling the Views it holds, and as the only "smart" thing living in the life cycle of the application, I see no reason it can't call the start of the next activity itself.
Furthermore, Activity/UIViewController lifecycle is too complex and too different from each other to be handled by the common java lib. It is something I view as a "detail" and not really "business rules", each platform needs to implement and worry about, thus making the code in the java lib more solid and not prone to change.
Again, my goal is to have each component of the app be as SRP (Single Responsibility Principle) as it can be, and this means linking as few things together as possible.
So an example of simple "normal" stuff:
(all examples are totally imaginary)
ActivityAllUsers displays a list of model object items. Those items came from calling AllUsersInteractor - a UseCase controllerin a back thread (which in turn also handled by the java lib with a dispatch to main thread when the request is completed). The user clicks on one of the items in this list. In this example the ActivityAllUsers already has the model object so opening ActivityUserDetail is a straightforward call with a bundle (or another mechanism) of this data model object. The new activity, ActivityUserDetail, is responsible of creating and using the correct UseCases if further actions are needed.
Example of complex logic call:
ActivityUserDetail has a button titled "Add as a friend" which when clicked calls the callback method onAddFriendClicked in the ActivityUserDetail:
public void onAddFriendClicked() {
AddUserFriendInteractor addUserFriend = new AddUserFriendInteractor();
int result = addUserFriend.add(this.user);
switch(result){
case AddUserFriendInteractor.ADDED:
start some animation or whatever
break;
case AddUserFriendInteractor.REMOVED:
start some animation2 or whatever
break;
case AddUserFriendInteractor.ERROR:
show a toast to the user
break;
case AddUserFriendInteractor.LOGIN_REQUIRED:
start the log in screen with callback to here again
break;
}
}
Example of even more complex call
A BroadcastReceiver on Android or AppDelegate on iOS receive a push notification. This is sent to NotificationHandler which is in the java lib logical layer. In the NotificationHandler constructor which is constructed once in the App.onCreate() it takes a ScreenHandler interface which you implemented in both platforms. This push notification is parsed and the correct method is called in the ScreenHandler to open the correct Activity.
The bottom line is: keep the View as dumb as you can, keep the Activity just smart enough to handle its own life cycle and handle its own views, and communicate with its own controllers (plural!), and everything else should be written (hopefully test-first ;) ) in the java lib.
Using these methods our app currently runs about 60-70% of its code in the java lib, with the next update should take it to the 70-80% hopefully.
I would recommend that you use some kind of slot mechanism. Similar to what other MVP frameworks use.
Definition: A slot is a part of a view where other views can be inserted.
In your presenter you can define as many slots as you want:
GenericSlot slot1 = new GenericSlot();
GenericSlot slot2 = new GenericSlot();
GenericSlot slot3 = new GenericSlot();
These slots must have a reference in the Presenter's view. You can implement a
setInSlot(Object slot, View v);
method. If you implement setInSlot in a view then the view can decide how it should be included.
Have a look at how slots are implemented here.
In cross platform development sharing what I call a "core" (the domain of your application written in Java), I tend to give to the UI ownership of which view to display next. That makes your application more flexible, adapting to the environment as needed (Using a UINavigationController on iOS, Fragments on Android and a single page with dynamic content on the web interface).
Your controllers should not be tied to a view, but instead fulfill a specific role (an accountController for logins/logouts, a recipeController for displaying and editing a recipe, etc).
You would have interfaces for your controllers instead of your views. Then you could use the Factory design pattern to instantiate your controllers on the domain side (your Java code), and the views on the UI side. The view factory has a reference to your domain's controller factory, and uses it to give to the requested view some controllers implementing specific interfaces.
Example:
Following a tap on a "login" button, a homeViewController asks the ViewControllerFactory for a loginViewController. That factory in turn asks the ControllerFactory for a controller implementing the accountHandling interface. It then instantiates a new loginViewController, gives it the controller it just received, and returns that freshly instantiated view controller to the homeViewController. The homeViewController then presents that new view controller to the user.
Since your "core" is environment-agnostic and only contains your domain & business logic, it should remain stable and less prone for edits.
You could take a look at this simplified demo project I made which illustrates this set-up (minus the interfaces).
This is an example app to demo a problem with nested fragments and wallet
https://github.com/zumper/WalletTest
Here is the structure of the app in terms of nesting
MainActivity
|
+-> TopLevelFragment
|
+-> NestedFragment
|
+-> SupportWalletFragment
The SupportWalletFragment is configured with a request code of 3333 via
public static final int WALLET_REQUEST_CODE = 3333;
...
WalletFragmentInitParams.Builder startParamsBuilder =
WalletFragmentInitParams.newBuilder()
.setMaskedWalletRequest(generateMaskedWalletRequest("10.99"))
.setMaskedWalletRequestCode(WALLET_REQUEST_CODE);
But when the payment method is selected from the wallet activity the result is delivered via
MainActivity.onActivityResult() with a request code value of 66036
and then to TopLevelFragment.onActivityResult() with a request code value of 500.
That's it. NestedFragment.onActivityResult() is never called with the expected request code of 3333
This problem seems to be a known issue:
http://blog.shamanland.com/2014/01/nested-fragments-for-result.html
https://code.google.com/p/android/issues/detail?id=40537
I can work around the problem in our real code by intercepting the onActivityResult() and relaying the params via event bus or something.
The tricky part is the fact that I don't even get the correct requestCode passed in... This makes all my hacks very brittle.
We have a fragment heavy application and I am unable to reduce the hierarchy more than I already have.
Are there other options for addressing this?
I have talked to google about this.. the answer is that there is no answer. If you are using SupportWalletFragment in a nested fragment situation, activity result propagation will not work (you need to handle it yourself) and your requestCode will not be honored for the MaskedWalletRequest (it will be honored for the FullWalletRequest).
If you are using native Fragments instead of support library Fragments, the requestCode will be honored but you will still need to deal with your own propagation.
We need to continue to use the support library.. So in our case, I am dealing with propagation of activity results manually, and I have set my requestCode to 500 to match the code I seem to get from the masked wallet response result. I think this is the best that can be done given the circumstances. I will update here if I get additional clarity.
How do I pass the arraylist to google cloud endpoint? It doesn't seem to work.
Edit start ----
Here is the signature of the endpoint with arraylist as input
public CollectionResponse<String> listDevices(#Named("devices") ArrayList<String> devices)
However when I iterate over this arraylist, I get all the records condensed into one. So even though I pass 10 strings, I get only one in my endpoint.
Edit end ----
I read somewhere that I should create a wrapper entity for arraylist and then pass it.
Edit start ----
So I created the entity containing the arraylist
#Entity
public class DEviceList {
ArrayList<String> devices;
}
and modified the signature as -
public CollectionResponse<String> listDevices(DeviceList devices)
Is it possible to pass object of DeviceList from client even though it not #Named? Can you provide the syntax?
My understanding is that since it's an entity it cannot be #Named, so while calling I need to inject it.
But google allows only three injected types -
1. com.google.appengine.api.users.User
2. javax.servlet.http.HttpServletRequest
3. javax.servlet.ServletContext
So above signature would not work.
So I changed the signature to -
public CollectionResponse<String> listDevices(HttpServletRequest request)
and inside I could get the entity as
DeviceList deviceList = (DeviceList)request.getAttribute("deviceList");
However I am not sure how to call this endpoint from the android client?
How I do pass the entity object using HTTPServletRequest?
Edit end ----
How do I do that? Can anyone cite an example?
The way you word your question, it seems the call is silently failing. That can't be. You must be receiving some kind of exception or log somewhere which could help you identify your issue better. You could read this article to refresh on cloud endpoint APIs and android.
If you are having trouble passing an arraylist of objects from your client to the API, I would suggest checking some things:
does the argument type in the API match what is being sent from the client? Do they both have access to the class definition?
if the data type inside the arraylist is a primitive, and it still fails, perhaps the advice you read "somewhere" was referring to the need (although I don't think this is the case) to use a wrapper object which simply contains one field - the arraylist, and pass that along the line?
If either the reminder to check your logs/error messages or any other info in this answer helped you resolve the issue, please remember to accept it, but not without first editing your post to explain how you resolved your issue.
I'm trying the new Android Mobile Backend. I did all the basic samples and now I want to set one or multiple filters. I tried only with one but the getCloudBackend().list returns a null list.
I have in the entity that I filter the parameter with the value. And if I remove the line where I filter, the service returns a list with data.
What I have to do if I want to filter by one parameter? and if I want to filter by the owner? and if I want to put more than one filter?
This is my code:
getCloudBackend().clearAllSubscription();
CloudQuery cq = new CloudQuery("MyEntity");
cq.setSort(CloudEntity.PROP_UPDATED_AT, Order.DESC);
cq.setLimit(100);
q.setFilter(F.eq("myparameter", "myvalue"));
cq.setScope(Scope.FUTURE_AND_PAST);
getCloudBackend().list(cq, handler);
You should post the error (or response) you get in the LogCat and also in the backend log. Filters are a bit tricky. I suggest you to check CloudQuery documentation to see the filter limitations, and CloudEntity to check the data you can send, retrieve and how.
About queries the documentation says:
Queries in the Cloud Backend API have some restrictions that originate
from the App Engine Datastore. By default, App Engine automatically
creates an index for each user defined property (with the exception of
List/Map properties). This index supports the following forms of
queries: - Kind name specified, no filters, no sorting - Queries with
only equality filters (F.eq) on indexed properties with no sorting -
Queries with only inequality filters, such as F.lt, F.ge, on a single
indexed property - Queries with one sort order on one property with no
filters
For example, for String properties you can only check for equality (eq/ne). You can't mix equaltiy filters with inequality ones. i.e. :
F.and(F.eq(PROPERTY_NAME, "Manuel"), F.gt(PROPERTY_AGE, Integer.valueOf(18)));
Will give you an error DatastoreNeedIndexException.
The documentation also says:
All other query forms will raise a DatastoreNeedIndexException on the
backend. To use a more complex form of queries, you need to add an
Index Configuration on the backend.
To do such you need edit datastore-indexes.xml in your backend. In order to do that you can use the command ./appengine-java-sdk/bin/appcfg.sh update_indexes myapp/war.
Looks promising though I haven't tested it yet...
Filters are case-sensitive, in the Guestbook example:
cq.setFilter(F.eq("MESSAGE","fabulous")); -> This won't work... don't let Google Cloud Console fool you.
cq.setFilter(F.eq("message","fabulous")); -> This will work... don't know why is case sensitive.
Let me know if this was the problem.
What I have to do if I want to filter by one parameter?
I think it is better to use getCloudBackend().listByProperty() method instead of getCloudBackend().list()
Example of this method usage:
getCloudBackend().listByProperty("yourKindName", "yourPropertyName", Op.EQ,
yourPropertyValueObject, null, 1, Scope.PAST, yourHandler);
and if I want to filter by the owner?
You can use the listed above method. Just put your owner property name instead of yourPropertyName
and if I want to put more than one filter?
To use multiple filter for one property I created listByPropertyAnd() method in CloudBackendAsync class:
public void listByPropertyAnd(String kindName, String propertyName,
CloudQuery.Order order, int limit, Scope scope,
CloudCallbackHandler<List<CloudEntity>> handler, F... filters) {
CloudQuery cq = new CloudQuery(kindName);
cq.setFilter(F.and(filters));
cq.setSort(propertyName, order);
cq.setLimit(limit);
cq.setScope(scope);
this.list(cq, handler);
}
Example of this method usage:
getCloudBackend().listByPropertyAnd("yourKindName", "yourKindName", Order.DESC,
1000, Scope.FUTURE_AND_PAST, yourHandler,
F.gt("yourKindName", yourFirstValue),
F.lt("yourKindName", yourSecondValue), F.eq("date", mToday));
In this case you can use any number of filters for one property but it should be compliant with datastore indexing rules.
Also it might be useful to read this Java Datastore Filters and this one Mobile Backend Starter - API Guide