Android DBFlow does not save objects - android

I have a database like this:
#Database(name = QuestionDatabase.NAME, version = QuestionDatabase.VERSION)
public class QuestionDatabase {
public static final String NAME = "QuestionDatabase"; // we will add the .db extension
public static final int VERSION = 1;
}
and a table like this:
#Table(database = QuestionDatabase.class)
public class Question extends BaseModel {
#PrimaryKey
public int localID;
#Column
public int Id;
#Column
public String Answer;
#Column
public String ImageURL;
#Column
public boolean IsFavorite;
#Column
public boolean IsSolved;
}
and an asynctask to retrive data from server:
public class QuestionRetriever extends AsyncTask<Integer, Void, Integer> {
private Activity callerActivity;
private QuestionAdapter questionsAdapter;
private List<Question> callerQuestions;
private Integer pageSize = 10;
public QuestionRetriever(Activity callerActivity, QuestionAdapter questionsAdapter, List<Question> questions){
this.callerActivity = callerActivity;
this.questionsAdapter = questionsAdapter;
this.callerQuestions = questions;
}
#Override
protected Integer doInBackground(Integer... pageNumbers) {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://192.168.1.33:313/")
.addConverterFactory(GsonConverterFactory.create())
.build();
QuestionWebService service = retrofit.create(QuestionWebService.class);
Call<List<Question>> call = service.getQuestionsPaged(pageNumbers[0].toString(), pageSize.toString());
try {
Response<List<Question>> excecuted = call.execute();
List<Question> questions = excecuted.body();
FastStoreModelTransaction
.insertBuilder(FlowManager.getModelAdapter(Question.class))
.addAll(questions)
.build();
callerQuestions.addAll(questions);
callerActivity.runOnUiThread(new Runnable() {
#Override
public void run() {
questionsAdapter.notifyDataSetChanged();
}
});
//Get TotalQuestionCount if not yet
if (((StatefulApplication) callerActivity.getApplication()).getQuestionCount() == -1){
Call<Integer> call2 = service.getQuestionsSize();
try {
((StatefulApplication) callerActivity.getApplication()).setQuestionCount(call2.execute().body());
} catch (IOException e) {
e.printStackTrace();
}
}
} catch (IOException e) {
e.printStackTrace();
}
catch (Exception e){
e.printStackTrace();
}
return 1;
}
#Override
protected void onPreExecute() {
super.onPreExecute();
//TODO: show loader
}
#Override
protected void onPostExecute(Integer result) {
super.onPostExecute(result);
//TODO: hide loader
}
}
as you see every thing seems ok and eve after running FastStoreModelTransaction nothing wrong happens. no errors.
the init job is done in splash screen activity like this:
private void initializeEveryRun() {
//Initializing DBFlow
//DBFlow needs an instance of Context in order to use it for a few features such as reading from assets, content observing, and generating ContentProvider.
//Initialize in your Application subclass. You can also initialize it from other Context but we always grab the Application Context (this is done only once).
FlowManager.init(new FlowConfig.Builder(getApplicationContext()).build());
}
any idea about what should cause this problem or any solution to try?
TG.

I found the answer!!!
As you see in the model, the Id is the identifier of the object retrieved from server and LocalId is the auto-increment identifier that is stored locally. This was the problem. I've used the Id field as Primary Key and added a field named OnlineId for server side identifer and everything is ok now.
Is this a bug or I was using that wrong?
TG.

This is not execute transaction, it's just transaction creation.
As you can see it this test DBFlow - FastModelTest.kt.
FastStoreModelTransaction
.insertBuilder(FlowManager.getModelAdapter(Question.class))
.addAll(questions)
.build();
You must execute your transaction like this :
FlowManager.getDatabase(QuestionDatabase.class).executeTransaction(<<YourTransaction>>);
Otherwise, if you already had a DatabaseWrapper instance you can do <<YourTransaction>>.excute(<<YourDatabaseWrapper>>);.

Related

How to retrieve auto generated ID from Room when using LiveData?

I am creating an Android application which would store lists of values, eg. temperature values for different times under a particular user-defined name. I intend to use SQLite for storing the values, and I read that using Room would provide an ORM layer to it, so I used it. But then I ran into an exception which basically said that I cannot open a database connection from the main thread, so I tried using LiveData for insertion and retrieval purposes. Now I have 2 tables. I'm just trying to show the structure of them without being syntactically accurate:
**PLACE_DETAILS**
place_id integer auto_increment
place_name string
latitude double
longitude double
**TEMPERATURE_DETAILS**
temperature_id integer auto_increment
place_id foreign key references place_details(place_id)
time_of_record timestamp
temperature float
Initially, I thought of not enforcing the foreign key relationship and just retrieving the generated key when I insert the PLACE_DETAILS object, like what Hibernate does, and using it in further insertions into the TEMPERATURE_DETAILS table. However, from this question:
Room API - How to retrieve recently inserted generated id of the entity?
I found that the DAO method itself would need to return a long value representing the generated ID.
#Insert
long insertPlaceDetails(PlaceDetails placeDetails);
However, the AsyncTask which runs in the ViewModel needs to override the doInBackground() method which has Void as the return.
public class PlaceDetailsViewModel extends AndroidViewModel {
private final LiveData<List<PlaceDetails>> placeDetailsList;
private PlaceDatabase placeDatabase;
public PlaceDetailsViewModel(#NonNull Application application) {
super(application);
placeDatabase = PlaceDatabase.getDatabase(this.getApplication());
placeDetailsList = placeDatabase.daoAccess().fetchAllPlaceDetails();
}
public LiveData<List<PlaceDetails>> getPlaceDetailsList() {
return placeDetailsList;
}
public void addPlace(final PlaceDetails placeDetails) {
Log.d("Adding", "Place: " + placeDetails.getPlaceName());
new addAsyncTask(placeDatabase).execute(placeDetails);
}
private static class addAsyncTask extends AsyncTask<PlaceDetails, Void, Void> {
private PlaceDatabase placeDatabase;
addAsyncTask(PlaceDatabase placeDatabase) {
this.placeDatabase = placeDatabase;
}
#Override
protected Void doInBackground(PlaceDetails... placeDetails) {
placeDatabase.daoAccess().insertPlaceDetails(placeDetails[0]);
return null;
}
}
}
So how could I actually retrieve the generated ID in my code? Also, if LiveData won't be able to provide me this value and relationship is the way to go, then also how do I insert values in the TEMPERATURE_DETAILS table based on the foreign key which has been auto-generated in the PLACE_DETAILS table? All the tutorials in the web give examples where they have given the id manually.
EDIT
According to the suggestions given by anhtuannd, I modified my VieModel class. But the value which is being returned is always -1. That itself shows that nothing is being inserted into the database. I have the WRITE_EXTERNAL_STORAGE permission in my manifest. Is anything else required for this to work?
public class PlaceDetailsViewModel extends AndroidViewModel {
private final LiveData<List<PlaceDetails>> placeDetailsList;
private PlaceDatabase placeDatabase;
private long insertId = -1;
public long getInsertId() {
return insertId;
}
public PlaceDetailsViewModel(#NonNull Application application) {
super(application);
placeDatabase = PlaceDatabase.getDatabase(this.getApplication());
placeDetailsList = placeDatabase.daoAccess().fetchAllPlaceDetails();
}
public LiveData<List<PlaceDetails>> getPlaceDetailsList() {
return placeDetailsList;
}
public void onPlaceInserted(long id) {
insertId = id;
}
public void addPlace(final PlaceDetails placeDetails) {
Log.d("Adding", "Place: " + placeDetails.getPlaceName());
new addAsyncTask(placeDatabase).execute(placeDetails);
}
private class addAsyncTask extends AsyncTask<PlaceDetails, Void, Void> {
private PlaceDatabase placeDatabase;
addAsyncTask(PlaceDatabase placeDatabase) {
this.placeDatabase = placeDatabase;
}
#Override
protected Void doInBackground(PlaceDetails... placeDetails) {
insertId = placeDatabase.daoAccess().insertPlaceDetails(placeDetails[0]);
return null;
}
#Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
onPlaceInserted(insertId);
}
}
}
I think you can get ID by using callback function in onPostExecute:
private static class addAsyncTask extends AsyncTask<PlaceDetails, Void, Void> {
private PlaceDatabase placeDatabase;
private PlaceDetails place;
private int insertId = -1;
addAsyncTask(PlaceDatabase placeDatabase) {
this.placeDatabase = placeDatabase;
}
#Override
protected Void doInBackground(PlaceDetails... placeDetails) {
place = placeDetails[0];
insertId = placeDatabase.daoAccess().insertPlaceDetails(place);
return null;
}
#Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
onPlaceInserted(insertId, place);
}
}
void onPlaceInserted(int id, PlaceDetails place) {
// you get ID here
}

SQL query with listview

I am busy with an application where i am getting data from my azure database with sql and storing it in an array. I created a separate class where i get my data and my main activity connects to this class and then displays it.
Here is my getData class:
public class GetData {
Connection connect;
String ConnectionResult = "";
Boolean isSuccess = false;
public List<Map<String,String>> doInBackground() {
List<Map<String, String>> data = null;
data = new ArrayList<Map<String, String>>();
try {
ConnectionHelper conStr=new ConnectionHelper();
connect =conStr.connectionclass(); // Connect to database
if (connect == null) {
ConnectionResult = "Check Your Internet Access!";
} else {
// Change below query according to your own database.
String query = "select * from cc_rail";
Statement stmt = connect.createStatement();
ResultSet rs = stmt.executeQuery(query);
while (rs.next()) {
Map<String,String> datanum=new HashMap<String,String>();
datanum.put("NAME",rs.getString("RAIL_NAME"));
datanum.put("PRICE",rs.getString("RAIL_UNIT_PRICE"));
datanum.put("RANGE",rs.getString("RAIL_RANGE"));
datanum.put("SUPPLIER",rs.getString("RAIL_SUPPLIER"));
datanum.put("SIZE",rs.getString("RAIL_SIZE"));
data.add(datanum);
}
ConnectionResult = " successful";
isSuccess=true;
connect.close();
}
} catch (Exception ex) {
isSuccess = false;
ConnectionResult = ex.getMessage();
}
return data;
}
}
And in my Fragmentactivity.java I simply just call the class as shown here:
List<Map<String,String>> MyData = null;
GetValence mydata =new GetValence();
MyData= mydata.doInBackground();
String[] fromwhere = { "NAME","PRICE","RANGE","SUPPLIER","SIZE" };
int[] viewswhere = {R.id.Name_txtView , R.id.price_txtView,R.id.Range_txtView,R.id.size_txtView,R.id.supplier_txtView};
ADAhere = new SimpleAdapter(getActivity(), MyData,R.layout.list_valence, fromwhere, viewswhere);
list.setAdapter(ADAhere);
list.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
HashMap<String,Object> obj=(HashMap<String,Object>)ADAhere.getItem(position);
String ID=(String)obj.get("A");
Toast.makeText(getActivity(), ID, Toast.LENGTH_SHORT).show();
}
});
My problem comes when I want to include the onPreExecute and onPostExecute because I am relatively new to android studio and I do not know where to put the following lines of code:
#Override
protected void onPreExecute() {
ProgressDialog progress;
progress = ProgressDialog.show(MainActivity.this, "Synchronising", "Listview Loading! Please Wait...", true);
}
#Override
protected void onPostExecute(String msg) {
progress.dismiss();
}
You need to get the data from your azure database using a background service or AsyncTask. However, you are defining a class GetData which does not extend AsyncTask and hence the whole operation is not asynchronous. And I saw you have implemented doInBackground method which is not applicable here as you are not extending AsyncTask. I would suggest an implementation like the following.
You want to get some data from your azure database and want to show them in your application. In these kind of situations, you need to do this using an AsyncTask to call the server api to get the data and pass the data to the calling activity using an interface. Let us have an interface like the following.
public interface HttpResponseListener {
void httpResponseReceiver(String result);
}
Now from your Activity while you want to get the data through an web service call, i.e. AsyncTask, just the pass the interface from the activity class to the AsyncTask. Remember that your AsyncTask should have an instance variable of that listener as well. So the overall implementation should look like the following.
public abstract class HttpRequestAsyncTask extends AsyncTask<Void, Void, String> {
public HttpResponseListener mHttpResponseListener;
private final Context mContext;
HttpRequestAsyncTask(Context mContext, HttpResponseListener listener) {
this.mContext = mContext;
this.mHttpResponseListener = listener;
}
#Override
protected String doInBackground(Void... params) {
String result = null;
try {
// Your implementation of getting data from your server
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
#Override
protected void onPostExecute(final String result) {
mHttpResponseListener.httpResponseReceiver(result);
}
#Override
protected void onCancelled() {
mHttpResponseListener.httpResponseReceiver(null);
}
}
Now you need to have the httpResponseReceiver function implemented in the calling Activity. So the sample activity should look like.
public class YourActivity extends AppCompatActivity implements HttpResponseListener {
// ... Other code and overriden functions
public void callAsyncTaskForGettingData() {
// Pass the listener here
HttpRequestAsyncTask getDataTask = new HttpRequestGetAsyncTask(
YourActivity.this, this);
getDataTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
#Override
public void httpResponseReceiver(String result) {
// Get the response callback here
// Do your changes in UI elements here.
}
}
To read more about how to use AsyncTask, you might consider having a look at here.

How to (RxJava) properly set conditions on which handler will process data, emitted from Observable?

So, my task is to push my device's Location info, when it changes, to the remote server Json API service. If remote server is unavailable, my DatabaseManager must save them to a local database.
Here is my Retrofit API:
public interface GpsService {
#POST("/v1/savelocationbatch")
SaveResponse saveLocationBatch(#Body LocationBatch locationBatch);
}
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(myBaseUrl)
.addConverterFactory(GsonConverterFactory.create())
.build();
GpsService service = retrofit.create(GpsService.class);
And a POJO class:
public class LocationBatch{
#SerializedName("LocationPointList")
ArrayList<LocationPoint> locationPointList;
#SerializedName("ClientId")
String clientId;
#SerializedName("ClientSecret")
String clientSecret;
//setter & getter
}
My LocationPoint model:
#Table(name="LocationPoints", id = "_id")
public class LocationPoint extends Model {
#SerializedName("Latitude")
#Column(name="latitude")
public Double latitude;
#SerializedName("Longitude")
#Column(name="longitude")
public Double longitude;
#SerializedName("Altitude")
#Column(name="altitude")
public Double altitude;
//... setters, getters etc
}
All of my last locations are stored in a CurrentLocationHolder singleton (for batch sending/saving to DB/emitting from Observable). It's setLocation() method updates currentLocation variable, then puts it to the locationBuffer, than checks the buffer's size, than if buffer's size exceeds my MAX_BUFFER_SIZE variable, it fires locationBufferChanged.onNext(with a copy of a locationBuffer as argument), then it clears locationBuffer...
public class CurrentLocationHolder {
private List<LocationPoint> locationBuffer =
Collections.synchronizedList(new ArrayList<>());
private LocationPoint currentLocation;
private final PublishSubject<List<LocationPoint>> locationBufferFull =
PublishSubject.create();
public Observable<List<LocationPoint>>
observeLocationBufferFull(boolean emitCurrentValue) {
return emitCurrentValue ?
locationBufferFull.startWith(locationBuffer) :
locationBufferFull;
}
public void setLocation(LocationPoint point) {
this.currentLocation = point;
locationBuffer.add(point);
if (locationBuffer.size() >= MAX_BUFFER_SIZE) {
locationBufferChanged.onNext(new ArrayList<>(this.locationBuffer));
}
locationBuffer.clear();
}
}
And here is my DatabaseManager:
public class DatabaseManager {
private Subscription locationBufferSubscription;
private static DatabaseManager instance;
public static void InitInstance() {
if (instance == null)
instance = new DatabaseManager();
}
}
public void saveToDb(ArrayList<LocationPoint> locArray){
ActiveAndroid.beginTransaction();
try {
for (int i = 0; i < locArray.size(); i++) {
locArray.get(i).save();
}
ActiveAndroid.setTransactionSuccessful();
}
finally {
ActiveAndroid.endTransaction();
}
}
}
My application's main goal:
To write all of the listened LocationPoints to the HTTP server through Retrofit. If a remote server will be suddenly down for some reason (or internet connection would lost), my app should seamlessly write new locationPoints to a local database. When the server (or internet) will be up, some mechanism should provide saved local data to Retrofit's call.
So, my questions are:
How to create an Rx-Observable object, which will emit List normally to a Retrofit service, but when server (or internet) goes down, it should provide unsaved LocationPoints to DatabaseManager.saveToDb() method?
How to catch internet connection or server "up" state? Is it a good idea to create some Observable, which will ping my remote server, and as result should emit some boolean value to it's subscribers? What is the best way to implement this behavior?
Is there a simple way to enqueue Retrofit calls with a locally saved data (from local DB), when internet connection (server) will become "up"?
How not to loose any of my LocationPoints on the server-side? (finally my client app must send all of them!
Am I doing something wrong? I am a newbie to Android, Java and
particularly to RxJava...
Interesting task! First of all: you don't need to create DB for storing such tiny info. Android has good place for storing any Serializable data.
So for saving locally data crate Model like:
public class MyLocation implements Serializable {
#Nonnull
private final String id;
private final Location location;
private final boolean isSynced;
// constructor...
// getters...
}
Singleton class:
public class UserPreferences {
private static final String LOCATIONS = "locations";
#Nonnull
private final SharedPreferences preferences;
#Nonnull
private final Gson gson;
private final PublishSubject<Object> locationRefresh = PublishSubject.create();
public void addLocation(MyLocation location) {
final String json = preferences.getString(LOCATIONS, null);
final Type type = new TypeToken<List<MyLocation>>() {
}.getType();
final List<MyLocation> list;
if (!Strings.isNullOrEmpty(json)) {
list = gson.fromJson(json, type);
} else {
list = new ArrayList<MyLocation>();
}
list.add(lication);
final String newJson = gson.toJson(set);
preferences.edit().putString(LOCATIONS, newJson).commit();
locationRefresh.onNext(null);
}
private List<String> getLocations() {
final String json = preferences.getString(LOCATIONS, null);
final Type type = new TypeToken<List<MyLocation>>() {
}.getType();
final List<MyLocation> list = new ArrayList<MyLocation>();
if (!Strings.isNullOrEmpty(json)) {
list.addAll(gson.<List<MyLocation>>fromJson(json, type));
}
return list;
}
#Nonnull
public Observable<List<MyLocation>> getLocationsObservable() {
return Observable
.defer(new Func0<Observable<List<MyLocation>>>() {
#Override
public Observable<List<MyLocation>> call() {
return Observable.just(getLocations())
.filter(Functions1.isNotNull());
}
})
.compose(MoreOperators.<List<MyLocation>>refresh(locationRefresh));
}
// also You need to create getLocationsObservable() and getLocations() methods but only for not synced Locations.
}
Change:
public interface GpsService {
#POST("/v1/savelocationbatch")
Observable<SaveResponse> saveLocationBatch(#Body LocationBatch locationBatch);
}
Now the most interesting...make it all works.
There is extention for RxJava. It has a lot of "cool tools" (btw, MoreOperators in UserPref class from there), also it has something for handling retrofit errors.
So let assume that location saving suppose to happens, when Observable saveLocationObservable emit something. In that case your code looks like:
final Observable<ResponseOrError<SaveResponse>> responseOrErrorObservable = saveLocationObservable
.flatMap(new Func1<MyLocation, Observable<ResponseOrError<SaveResponse>>>() {
#Override
public Observable<ResponseOrError<SaveResponse>> call(MyLocation myLocation) {
final LocationBatch locationBatch = LocationBatch.fromMyLocation(myLocation); // some method to convert local location to requesr one
return saveLocationBatch(locationBatch)
.observeOn(uiScheduler)
.subscribeOn(networkScheduler)
.compose(ResponseOrError.<SaveResponse>toResponseOrErrorObservable());
}
})
.replay(1)
.refCount();
final Observable<Throwable> error = responseOrErrorObservable
.compose(ResponseOrError.<SaveResponse>onlyError())
.withLatestFrom(saveLocationObservable, Functions2.<MyLocation>secondParam())
.subscribe(new Action1<MyLocation>() {
#Override
public void call(MyLocation myLocation) {
// save location to UserPref with flag isSynced=flase
}
});
final Observable<UserInfoResponse> success = responseOrErrorObservable
.compose(ResponseOrError.<SaveResponse>onlySuccess())
.subscribe(new Action1<SaveResponse>() {
#Override
public void call(SaveResponse response) {
// save location to UserPref with flag isSynced=true
}
});

Async task does not let me override onPostExecute (method does not override method from it's superclass)

i'm creating an app that grabs list of playlists on YouTube. It used to be a list of videos, but i've changed the code and now it did not let me override that method.
My understanding is that i should change the "extends AsyncTask" with the new Playlist value instead of Video as it was before, but it still did not work.
it was:
public abstract class GetPlaylistAsyncTask extends AsyncTask<String, Void, Pair<String, List<Video>>> {
and now is:
public abstract class GetPlaylistAsyncTask extends AsyncTask<String, Void, Pair<String, List<Playlist>>> {
This is where is the error:
mPlaylist = new Playlist(mPlaylistId);
initAdapter(mPlaylist);
new GetPlaylistAsyncTask(mYouTubeDataApi, mTitle, mSearchQuery) {
#Override
public void onPostExecute(Pair<String, List<Playlist>> result) {
handleGetPlaylistResult(mPlaylist, result);
}
}.execute(mPlaylist.playlistId, mPlaylist.getNextPageToken());
And this is the AsyncTask:
public abstract class GetPlaylistAsyncTask extends AsyncTask<String, Void, Pair<String, List<Playlist>>> {
private static final String TAG = "GetPlaylistAsyncTask";
private static final Long YOUTUBE_PLAYLIST_MAX_RESULTS = 50L;
private static final String YOUTUBE_VIDEOS_PART = "snippet,contentDetails,statistics"; // video resource properties that the response will include.
private static final String YOUTUBE_VIDEOS_FIELDS = "items(id,snippet(title,description,thumbnails/high),contentDetails/duration,statistics)"; // selector specifying which fields to include in a partial response.
private YouTube mYouTubeDataApi;
private String mTitle;
private String mSearchQuery;
public GetPlaylistAsyncTask(YouTube api, String title, String searchQuery) {
mYouTubeDataApi = api;
mTitle = title;
mSearchQuery = searchQuery;
}
#Override
protected Pair<String, List<Playlist>> doInBackground(String... params) {
SearchListResponse searchResponse;
try {
YouTube.Search.List search = mYouTubeDataApi.search().list("id,snippet");
search.setKey(ApiKey.YOUTUBE_API_KEY);
search.setQ(mTitle + " " + mSearchQuery);
search.setType("video");
search.setFields("items(id/kind,id/videoId,snippet/title,snippet/thumbnails/default/url)");
search.setMaxResults(YOUTUBE_PLAYLIST_MAX_RESULTS);
searchResponse = search.execute();
} catch (IOException e) {
e.printStackTrace();
return null;
}
if (searchResponse == null) {
Log.e(TAG, "Failed to get playlist");
return null;
}
ArrayList videoIds = new ArrayList();
for (SearchResult item : searchResponse.getItems()) {
videoIds.add(item.getId().getVideoId());
}
VideoListResponse videoListResponse = null;
try {
videoListResponse = mYouTubeDataApi.videos()
.list(YOUTUBE_VIDEOS_PART)
.setFields(YOUTUBE_VIDEOS_FIELDS)
.setKey(ApiKey.YOUTUBE_API_KEY)
.setId(TextUtils.join(",", videoIds)).execute();
} catch (IOException e) {
e.printStackTrace();
}
return new Pair(searchResponse.getNextPageToken(), videoListResponse.getItems());
}
}
i don't get what is wrong on my code, i'll appreciate if you can point me on the right direction. Thanks in advance!
I've figured it out. The "onPostExecute" takes the object that doInBackground returns, as this post says:
stackoverflow post
Thanks to all for your answers, especially to #Enzokie
Just change public to protected since onPostExecute is not public by design.
#Override
protected void onPostExecute(Pair<String, List<Playlist>> result) {
handleGetPlaylistResult(mPlaylist, result);
}

AsyncTask Android - Design Pattern and Return Values

I'm writing an application that validates login credentials on an external webserver - so I have the basic issue of creating a login screen that when submitted will send an HTTP request to a server in the background and not cause the UI to hang - whilst providing a ProgressDialog to the user.
My problem lies in, I want to write a generic HTTP Request class that extends AsyncTask, so when I call .execute() I will then pass String parameters which may contain something like 'post', and when doInBackground is called this will see the 'post' string and then forward those parameters onto the respective call in my class. Pseudo code would be something like
public class HTTPOperations extends AsyncTask<String, Void, String>
{
doInBackground(String... string1,additionalParams)
{
if string1.equals "post"
response = httpPost(additionalParams)
return response;
}
httpPost(params)
{
// do http post request
}
}
This is all I could think of, other than creating a class for every HTTP Post/GET etc request I wish to make and extending ASyncTask...
Which leads me to my next problem, if the HTTP POST is successful and it returns an authentication token, how do I access this token?
Because new httpOperations.execute(), does not return the string from doInBackground, but a value of type
Sorry if this doesn't make sense, I can't figure this out at all. Please ask for elaboration if you need it. AsyncTask design patterns and ideas are hugely welcomed.
If you are designing a reusable task for something like this, you need to identify a reusable return type. Its a design decision on your part. Ask yourself, "Are my HTTP operations similar in both the mechanisms with which they are called and in which their data is processed?" If so, you can design a single class to do both. If not, you probably need different classes for your different remote operations.
In my personal use, I have an object i attach key value pairs to and the common return type is the HttpEntity. This is the return type for both HTTP Get and Post, and this seems to work ok in my scenarios because i throw exceptions in exceptional HTTP result situations, like 404. Another nice aspect of this setup is that the code to attach parameters to a get or post are fairly similar, so this logic is pretty easy to construct.
An example would be something like this (psuedo):
public interface DownloadCallback {
void onSuccess(String downloadedString);
void onFailure(Exception exception);
}
Then in your code, where you go to do the download:
DownloadCallback dc = new DownloadCallback(){
public void onSuccess(String downloadedString){
Log.d("TEST", "Downloaded the string: "+ downloadedString);
}
public void onFailure(Exception e){
Log.d("TEST", "Download had a serious failure: "+ e.getMessage());
}
}
DownloadAsyncTask dlTask = new DownloadAsyncTask(dc);
Then inside the constructor of DownloadAsyncTask, store the DownloadCallback and, when the download is complete or fails, call the method on the download callback that corresponds to the event. So...
public class DownloadAsyncTask extends AsyncTask <X, Y, Z>(){
DownloadCallback dc = null;
DownloadAsyncTask(DownloadCallback dc){
this.dc = dc;
}
... other stuff ...
protected void onPostExecute(String string){
dc.onSuccess(string);
}
}
I'm going to reiterate that I think for the good of yourself, you should pass back HttpEntities. String may seem like a good idea now, but it really leads to trouble later when you want to do more sophisticated logic behind your http calls. Of course, thats up to you. Hopefully this helps.
suppose the data format with web api is json, my design pattern :
common classes
1.MyAsyncTask : extends AsyncTask
2.BackgroundBase : parameters to server
3.API_Base : parameters from server
4.MyTaskCompleted : callback interface
public class MyAsyncTask<BackgroundClass extends BackgroundBase,APIClass extends API_Base> extends AsyncTask<BackgroundClass, Void, APIClass> {
private ProgressDialog pd ;
private MyTaskCompleted listener;
private Context cxt;
private Class<APIClass> resultType;
private String url;
private int requestCode;
public MyAsyncTask(MyTaskCompleted listener, Class<APIClass> resultType, int requestCode, String url){
this.listener = listener;
this.cxt = (Context)listener;
this.requestCode = requestCode;
this.resultType = resultType;
this.url = url;
}
public MyAsyncTask(MyTaskCompleted listener, Class<APIClass> resultType, int requestCode, String url, ProgressDialog pd){
this(listener, resultType, requestCode, url);
this.pd = pd;
this.pd.show();
}
#Override
protected APIClass doInBackground(BackgroundClass... params) {
APIClass result = null;
try {
//do something with url and params, and get data from WebServer api
BackgroundClass oParams = params[0];
String sUrl = url + "?d=" + URLEncoder.encode(oParams.getJSON(), "UTF-8");
String source = "{\"RtnCode\":1, \"ResultA\":\"result aaa\", \"ResultB\":\"result bbb\"}";
//to see progressdialog
Thread.sleep(2000);
result = new com.google.gson.Gson().fromJson(source, resultType);
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
#Override
protected void onPostExecute(APIClass result) {
super.onPostExecute(result);
try {
if(pd != null && pd.isShowing())
pd.dismiss();
API_Base oApi_Base = (API_Base)result;
listener.onMyTaskCompleted(result , this.requestCode);
} catch (Exception e) {
e.printStackTrace();
}
}
}
public class API_Base {
public int RtnCode;
public String getJSON(Context context) throws Exception
{
return new com.google.gson.Gson().toJson(this);
}
public String toString(){
StringBuilder sb = new StringBuilder();
for (Field field : this.getClass().getFields()) {
try {
field.setAccessible(true);
Object value = field.get(this);
if (value != null) {
sb.append(String.format("%s = %s\n", field.getName(), value));
}
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
return sb.toString();
}
}
public class BackgroundBase {
public String getJSON() throws Exception
{
return new com.google.gson.Gson().toJson(this);
}
}
public interface MyTaskCompleted {
void onMyTaskCompleted(API_Base oApi_Base, int requestCode) ;
}
example, let's call two api in one activity
assume :
API 1.http://www.google.com/action/a
input params : ActionA
output params : RtnCode, ResultA
API 2.http://www.google.com/action/b
input params : ActionB
output params : RtnCode, ResultB
classes with example :
1.MyActivity : extends Activity and implements MyTaskCompleted
2.MyConfig : utility class, i set requestCode here
3.BackgroundActionA, BackgroundActionB : model classes for api's input params
4.API_ActionA, API_ActionB : model classes for api's output params
public class MyActivity extends Activity implements MyTaskCompleted {
ProgressDialog pd;
Button btnActionA, btnActionB;
TextView txtResult;
#Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.my_layout);
btnActionA = (Button)findViewById(R.id.btn_actionA);
btnActionB = (Button)findViewById(R.id.btn_actionB);
txtResult = (TextView)findViewById(R.id.txt_result);
btnActionA.setOnClickListener(listener_ActionA);
btnActionB.setOnClickListener(listener_ActionB);
pd = new ProgressDialog(MyActivity.this);
pd.setTitle("Title");
pd.setMessage("Loading");
}
Button.OnClickListener listener_ActionA = new Button.OnClickListener(){
#Override
public void onClick(View v) {
//without ProgressDialog
BackgroundActionA oBackgroundActionA = new BackgroundActionA("AAA");
new MyAsyncTask<BackgroundActionA, API_ActionA>(MyActivity.this,
API_ActionA.class,
MyConfig.RequestCode_actionA,
"http://www.google.com/action/a").execute(oBackgroundActionA);
}
};
Button.OnClickListener listener_ActionB = new Button.OnClickListener(){
#Override
public void onClick(View v) {
//has ProgressDialog
BackgroundActionB oBackgroundActionB = new BackgroundActionB("BBB");
new MyAsyncTask<BackgroundActionB, API_ActionB>(MyActivity.this,
API_ActionB.class,
MyConfig.RequestCode_actionB,
"http://www.google.com/action/b",
MyActivity.this.pd).execute(oBackgroundActionB);
}
};
#Override
public void onMyTaskCompleted(API_Base oApi_Base, int requestCode) {
// TODO Auto-generated method stub
if(requestCode == MyConfig.RequestCode_actionA){
API_ActionA oAPI_ActionA = (API_ActionA)oApi_Base;
txtResult.setText(oAPI_ActionA.toString());
}else if(requestCode == MyConfig.RequestCode_actionB){
API_ActionB oAPI_ActionB = (API_ActionB)oApi_Base;
txtResult.setText(oAPI_ActionB.toString());
}
}
}
public class MyConfig {
public static String LogTag = "henrytest";
public static int RequestCode_actionA = 1001;
public static int RequestCode_actionB = 1002;
}
public class BackgroundActionA extends BackgroundBase {
public String ActionA ;
public BackgroundActionA(String actionA){
this.ActionA = actionA;
}
}
public class BackgroundActionB extends BackgroundBase {
public String ActionB;
public BackgroundActionB(String actionB){
this.ActionB = actionB;
}
}
public class API_ActionA extends API_Base {
public String ResultA;
}
public class API_ActionB extends API_Base {
public String ResultB;
}
Advantage with this design pattern :
1.one Advantage for multi api
2.just add model classes for new api, ex: BackgroundActionA and API_ActionA
3.determine which API by different requestCode in callback function : onMyTaskCompleted

Categories

Resources