I have populated data from server in UI through realm and volley.
Is it possible to store that data locally and show in UI from local db? And how to do it?
Any example would be very helpful.Thanks.
Actually I am trying to get the data locally after fetching and populating the data from the server like this.
if(realm!=null){
Log.d(AppConstants.TAG, "realm fetching");
RealmResults<SellerProducts> sellerProductItems=realm.where(SellerProducts.class).findAll();
adapter.setData(sellerProductItems);
adapter.notifyDataUpdate();
}else {
//network fetch operation
// getting data
//populating data like this
sellerProductItems= gson.fromJson(products.toString(), new TypeToken<List<SellerProducts>>(){}.getType());
//Products is from server response
realm.beginTransaction();
realm.copyToRealmOrUpdate(sellerProductItems);
realm.commitTransaction();
adapter.setData(sellerProductItems);
adapter.notifyDataUpdate();
}
Is it correct?
To save data in realm create a realm object and do Transaction like this
myRealm.beginTransaction();
// Create an object
Country country1 = myRealm.createObject(Country.class);
country1.setName("Norway");
country1.setPopulation(5165800);
country1.setCode("NO");
myRealm.commitTransaction();
To read the data saved
RealmResults<Country> results1 =
myRealm.where(Country.class).findAll();
for(Country c:results1) {
Log.d("results1", c.getName());
}
For complete information visit
http://code.tutsplus.com/tutorials/up-and-running-with-realm-for-android--cms-25241
No, you're completely wrong. If you're doing it right, realm can never be null at that point in your code.
Anyways it works vaguely like this (based on this):
public class GsonRequest<T extends RealmObject> extends Request<T> {
private final Gson gson = new Gson();
private final Listener<T> listener;
/**
* Make a GET request and return a parsed object from JSON.
*
* #param url URL of the request to make
*/
public GsonRequest(Method method, String url,
Listener<T> listener, ErrorListener errorListener) {
super(method, url, errorListener);
this.listener = listener;
}
#Override
protected void deliverResponse(T response) {
listener.onResponse(response);
}
#Override
protected Response<T> parseNetworkResponse(NetworkResponse response) {
try {
String json = new String(
response.data,
HttpHeaderParser.parseCharset(response.headers));
final List<T> result = gson.fromJson(json, new TypeToken<ArrayList<T>>() {}.getType());
Realm realm = null;
try {
realm = Realm.getInstance(realmConfiguration); //get realm config
realm.executeTransaction(new Realm.Transaction() {
for(T t : result) {
realm.copyToRealmOrUpdate(t);
}
});
} finally {
if(realm != null) {
realm.close();
}
}
return Response.success(null, //returning null because
//Realm handles all reload of data on UI thread
HttpHeaderParser.parseCacheHeaders(response));
} catch (UnsupportedEncodingException e) {
return Response.error(new ParseError(e));
} catch (JsonSyntaxException e) {
return Response.error(new ParseError(e));
}
}
}
And
Realm realm;
RealmResults<SellerProducts> results;
final RealmChangeListener<RealmResults<SellerProducts>> realmChangeListener;
SellerProductsAdapter adapter;
#Override
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
realm = Realm.getInstance(this);
results = realm.where(SellerProducts.class).findAll();
realmChangeListener = new RealmChangeListener<RealmResults<SellerProducts>>() {
#Override
public void onChange(RealmResults<SellerProducts> element) {
adapter.notifyDataSetChanged();
}
}
setContentView(R.layout.retrofit_is_better_than_volley);
ListView whyIsThisNotRecyclerView = (ListView) findViewById(R.id.not_recycler_for_some_reason_view);
adapter = new SellerProductsAdapter(this, results);
whyIsThisNotRecyclerView.setAdapter(adapter);
results.addChangeListener(realmChangeListener);
}
#Override
public void onDestroy() {
if(results != null && results.isValid()) {
results.removeChangeListener(realmChangeListener);
}
if(realm != null) {
realm.close();
}
super.onDestroy();
}
And then something like
GsonRequest<SellerProducts> request = new GsonRequest(Method.GET, url,
new Response.Listener<SellerProducts>() {
#Override
public void onResponse(SellerProducts nullObject) {
// hide dialog or something
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
Log.d(TAG, "Error: " + error.getMessage()/*, exception? */);
// hide dialog or something
}
});
//add to request queue
If you are planning to bring Realm to your production code, it takes more than what is discussed here, Realm is a major library and there are also some limitations like Realm, RealmObject and RealmResults can not be passed across threads.
To solve this problem, you need a good architecture, where Realm is isolated from rest of the code. Create a realmModel for each jsonModel and a DAO (Data Access Object). All Realm related calculations should be part of DAO, so that none of your code base needs to know about Realm.
Here is an article about Realm best practices with a good architechture https://medium.com/#Viraj.Tank/realm-integration-in-android-best-practices-449919d25f2f
Also a sample project demonstrating Integration of Realm on Android with MVP(Model View Presenter), RxJava, Retrofit, Dagger, Annotations & Testing. https://github.com/viraj49/Realm_android-injection-rx-test
Related
i am new to Android,here is class Question bank returns list of json obj received from an api
ArrayAsyncResponse in interface containing only one method process complete ,i readed that http request is asynchronous but unable to relate
Question is model class
case 1) when there is no ArrayAsyncResponse interface exist and i return the list to main activity and print it it shows empty list but when i make call to callback.processComplete() and then return list followed by printing ,it shows data
case2 )if i pass null in this function callback.processComplete() then again returned list is empty
so what basically Interface is helping us
public class QuestionBank {
ArrayList questionArrayList = new ArrayList<>();
private String url = "https://raw.githubusercontent.com/curiousily/simple-quiz/master/script/statements-data.json";
public List<Question> getQuestions(final AnswerListAsyncResponse callBack) {
JsonArrayRequest jsonArrayRequest = new JsonArrayRequest(
Request.Method.GET,
url,
(JSONArray) null,
new Response.Listener<JSONArray>() {
#Override
public void onResponse(JSONArray response) {
for (int i = 0; i < response.length(); i++) {
try {
Question question = new Question();
question.setAnswer(response.getJSONArray(i).get(0).toString());
question.setAnswerTrue(response.getJSONArray(i).getBoolean(1));
//Add question objects to list
questionArrayList.add(question);
//Log.d("Hello", "onResponse: " + question.getAnswer());
// Log.d("JSON", "onResponse: " + response.getJSONArray(i).get(0));
//Log.d("JSON2", "onResponse: " + response.getJSONArray(i).getBoolean(1));
} catch (JSONException e) {
e.printStackTrace();
}
}
if (null != callBack) callBack.processFinished(questionArrayList);
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
}
});
AppController.getInstance().addToRequestQueue(jsonArrayRequest);
return questionArrayList;
}
public class MainActivity extends AppCompatActivity {
private QuestionBank questionBank;
List<Question> questionList;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
questionBank=new QuestionBank();
questionList=questionBank.getQuestions(new AnswerListAsyncResponse() {
#Override
public void processFinished(ArrayList<Question> questionsArrayList) {//this function triggers when response is received from api
Log.d("inside", "processFinished: "+questionsArrayList);
}
});
Log.d("sync response", "questionLIst: "+questionList);
}
}
Sorry I can't write this in comment, but I think I can help you.
I don't understand well your question. but I think you need to get a Listener of the async in your class activity.
You can do that with EventBus, like this : https://github.com/greenrobot/EventBus
In MVP android i believe the network layer (retrofit , volley etc) should NOT be apart of the model. But I need a firm example on how to construct the model then. Should the model be a singleton that the network layer simply creates when api call completes ?
Lets take a look at a presenter i have for my one of my activities:
public class MainActivityPresenter implements IMainPresenterContract, Callback {
IMainActivityViewContract view;//todo set up a weak reference to View to avoid leakage
NewsService interactor;
public MainActivityPresenter(IMainActivityViewContract view, NewsService interactor) {
this.view = view;
this.interactor = interactor;
}
public void loadResource() {
interactor.loadResource();
}
public void onRequestComplete(final NewsEntities newsEntities) {
view.dataSetUpdated(newsEntities.getResults());
}
#Override
public void onResult(final NewsEntities newsEntities) {
onRequestComplete(newsEntities);
}
public void goToDetailsActivity(Result result) {
view.goToDetailsActivity(result);
}
}
So my question is about the NewsService interactor parameter i am passing into the constructor. I was assuming this should be model data and not a networking service. But what should it look like then ? Currently mine looks like this:
public class NewsService implements INewsServiceContract {
private Gson gson;
private Callback mCallback;
public NewsService() {
configureGson();
}
private static String readStream(InputStream in) {
StringBuilder sb = new StringBuilder();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(in))) {
String nextLine;
while ((nextLine = reader.readLine()) != null) {
sb.append(nextLine);
}
} catch (IOException e) {
e.printStackTrace();
}
return sb.toString();
}
public void setCallBack(Callback cb) {
mCallback = cb; // or we can set up event bus
}
private void configureGson() {
GsonBuilder builder = new GsonBuilder();
builder.excludeFieldsWithoutExposeAnnotation();
gson = builder.create();
}
#Override
public void loadResource() {
//Todo could use a loader instead help with the config change or a headless fragment
new AsyncTask<String, String, String>() {
#Override
protected String doInBackground(String... params) {
String readStream = "";
HttpURLConnection con = null;
try {
URL url = new URL("https://api.myjson.com/bins/nl6jh");
con = (HttpURLConnection) url.openConnection();
readStream = readStream(con.getInputStream());
} catch (Exception e) {
e.printStackTrace();
}
finally {
if(con!=null)
con.disconnect();
}
return readStream;
}
#Override
protected void onPostExecute(String result) {
super.onPostExecute(result);
NewsService.this.onRequestComplete(result);
}
}.execute();
}
private void onRequestComplete(String data) {
data = data.replaceAll("\"multimedia\":\"\"", "\"multimedia\":[]");
news.hotels.com.sample.Model.NewsEntities newsEntities = gson.fromJson(data, NewsEntities.class);
mCallback.onResult(newsEntities);
}
}
so as you can see the NewsService in this case is doing the network calls. I think i should not have passed this into the presenter. But how can the model be constructed then ? who calls the NewsService ?
UPDATE: THIS QUESTION WAS a long time ago, everyone please use clean architecture approach, and let your presenter know nothing about the network layer.
the network calls need to be in Model layer and should be triggered from the presenter. but its the Model layer who decides where to et the data and its hidden from the Presenter layer.
I myself use an interactor class to do this that is in model layer and a presenter will use this interactor to get the data and the interactor will get the data from DB or Server regarding to the situation.
look at this sample project in my repo:
https://gitlab.com/amirziarati/Echarge
I used Dagger to do DI that may confuse you. just look at packaging and how i seperate concerns between layers.
UPDATE: I used presenter to sync data from server and DB which is WRONG. the presenter should know nothing about this proccess. I didnt recognize this problem that time.
I have a Realm object called Message (snippet below) which can also have nested messages of the same type in it:
public class Message extends RealmObject {
#PrimaryKey
private int id;
#Getter
#Setter
private RealmList<Message> nestedMessages;
}
At some point I need to update the list of nested messages e.g if a new nested message is added.
I'm doing that like this:
//newMessage is returned from a request
realm.beginTransaction();
RealmList<Message> nestedMessages = initialMessage.getNestedMessages();
nestedMessages.add(newMessage);
initialMessage.setNestedMessages(nestedMessages);
realm.copyToRealmOrUpdate(initialMessage);
realm.commitTransaction();
//Handle exceptions...
But after this the inner object "nestedMessages" is reset and the size is zero.
Is there something I'm missing here? Or does Realm works in a different way for this kind of use case?
NewMessage is the result of this response:
protected Response<Message> parseNetworkResponse(NetworkResponse response) {
if (isReqSuccessful(response)) {
try {
Gson gson = realmGson();
String json = new String(
response.data,
HttpHeaderParser.parseCharset(response.headers));
return Response.success(
gson.fromJson(json, Message.class),
HttpHeaderParser.parseCacheHeaders(response));
} catch (UnsupportedEncodingException e) {
return Response.error(new ParseError(e));
} catch (JsonSyntaxException e) {
return Response.error(new ParseError(e));
}
}
return Response.error(new VolleyError());
}
If your newMessage is defined by gson.fromJson(json, Message.class), there are only a couple of things you need to do to add it to initialMessage.nestedMessages.
Begin transaction
Insert newMessage into realm
Add it to nestedMessages.
Commit transaction
Beginning and committing a transaction (and cancelling in case of failure) is automatically handled by executeTransaction().
Should be as simple as this
Message newMessage = parseNetworkResponse bla bla
realm.executeTransaction(new Realm.Transaction() { // 1
#Override
public void execute(Realm realm) {
newMessage = realm.copyToRealmOrUpdate(newMessage); // 2
initialMessage.getNestedMessages().add(newMessage); // 3
}
}); // 4
There is no need for realm.copyToRealmOrUpdate(initialMessage);, since it will automatically be updated when you add an item to nestedMessages and commit that.
Also RealmList<Message> nestedMessages = initialMessage.getNestedMessages(); is an unnecessary copy and requires more memory so I suggest you don't do that either.
Ehm no realm doesn't work this way. I don't really know how it interacts with lists but try the first solution, if it doesn't work the second one will definitely do:
1.
realm.executeTransaction(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
RealmList<Message> nestedMessages = initialMessage.getNestedMessages();
newMessage = realm.copyToRealmOrUpdate(newMessage);
nestedMessages.add(newMessage);
}
});
2.
RealmList<Message> nestedMessages = new RealmList<Message>();
RealmList<Message> oldMessages = initialMessage.getNestedMessages();
for(Message message: oldMessages){
nestedMessages.add(message);
}
newMessage = realm.copyToRealmOrUpdate(newMessage);
nestedMessages.add(newMessage);
initialMessage.setNestedMessages(nestedMessages);
realm.copyToRealmOrUpdate(initialMessage);
if (isConnected()) {
Event eInstance = new Event();
theEvents = eInstance.downloadEvents(eventsNightlife, getActivity());
rAdapter = new RecyclerAdapter(theEvents);
recyclerView.setAdapter(rAdapter);
progrsBar.setVisibility(View.GONE);
....
This is part of the code that runs at "onCreateView". The method downloadEvents uses Volley to download JSON data, extract it and return a list of items (theEvents). Now when my app starts, the recycler view is empty. If I go to my home screen out of the app and then run my app again, this time the data sometimes gets downloaded.
I debugged step by step, and at first launch (i mean when the app is not just resuming), theEvents is empty, so the download didn't return or manage to return anything...
Suggestions on how to execute things before the UI has been shown to the user or what actually needs to be done to approach this task better?
Also, I use a swipeRefreshLayout and at its onRefresh method I do:
public void onRefresh() {
Event eInstance = new Event();
theEvents = eInstance.downloadEvents(eventsNightlife, getActivity());
rAdapter.notifyDataSetChanged();
swipeRefreshLayout.setRefreshing(false);
}
but it doesn't work. I also tried to
rAdapter = new RecyclerAdapter(theEvents);
rAdapter.notifyDataSetChanged();
recyclerView.swapAdapter(rAdapter, false);
still not working.
EDIT: My downloadEvents method implementing Volley:
public List<Event> downloadEvents(String urlService, Context context) {
eventsList = new ArrayList<>();
RequestQueue requestQueue = Volley.newRequestQueue(context);
JsonArrayRequest jsonArrayRequest = new JsonArrayRequest
(Request.Method.GET, urlService, null, new Response.Listener<JSONArray>() {
#Override
public void onResponse(JSONArray response) {
try {
String durationStr = null;
for (int i = 0; i < response.length(); i++) {
JSONObject eventJson = response.getJSONObject(i);
String title = eventJson.getString("EventTitle");
String body = eventJson.getString("EventBody");
String date = eventJson.getString("EventDate");
String time = eventJson.getString("EventTime");
int duration = Integer.parseInt(eventJson.getString("EventDuration"));
if (duration > 60) {
durationStr = "Duration: " + duration / 60 + " h";
} else if (duration < 60) {
durationStr = "Duration: " + duration + " m";
}
String place = eventJson.getString("EventPlace");
String organ = eventJson.getString("Organization");
Event event = new Event(title, body, date, time, durationStr, place, organ);
eventsList.add(event);
}
} catch (JSONException e) {
e.printStackTrace();
}
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
Log.e("VOLLEY ERROR", "" + error);
}
}
);
requestQueue.add(jsonArrayRequest);
return eventsList;
}
You can use EventBus for your purpose that is a simple and truth way.
Here, i write an example for how to use EventBus with volley.
Consider that i want to download some data.
This is the class that my download methods is inside it (you can add more methods to it in the future):
Im used volley to download my data:
// Download methods is inside volley
public class MyDownloader{
public static void downloadData(){
DownloadDataEvent dlDataEvent=new DownloadDataEvent();
List<String> myResult=new ArrayList<>();
...
#Override
public void onResponse(JSONArray response) {
super.onResponse(response);
if(respone!=null){
// Do what i want with my received data
dlDataEvent.setData(response);
}
// Post my event by EventBus
EventBus.getDefault().post(dlDataEvent);
...
}
}
}
This is my event:
public class DownloadDataEvent{
private JSONArray mData;
public void setData(JSONArray data){
mData=data;
}
public JSONArray setData(){
return mData;
}
}
Now i want to use my downloadData() method inside my MainActivity:
(I called my downloadData method inside onCreate.)
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
...
// I have to register this class for EventBus subscriber:
if(!EventBus.getDefault().isRegister(this)){
EventBus.getDefault().registerSticky(this);
}
// Call my downloadData method
if(isConnected()){
MyDownloader.downloadData();
}
}
// And for receive the data through EventBus, i have to create a
// method (subscriber) in this template:
public void onEventMainThread(DownloadDataEvent downloadDataEvent){
JSONArray result=downloadDataEvent.getData();
// Do what i want with my received data
}
}
you can create more than one subscriber every where you want to use received data.
I passed JSONArray to my DownloadDataEvent that it is not good. you can deserialize your received data and pass it to your DownloadDataEvent.
I used Volley to download data
Maybe my descriptions were confusing, but EventBus is a well-known library and is very easy to use.
I need to parse list of object, whith can be emply. {"data":[]}
I use tamplated callback CallBack<T>called with
public static DataList {
public List<Data> data
};
api.getData(new Callback<DataList>() {...});
it crashed with error:java.lang.ClassCastException: com.google.gson.internal.LinkedTreeMap cannot be cast to com...DataList
Please help
Your model should work fine. Perhaps your server isn't returning what you think it does, or maybe its not application/json what it's returning?
Here's a quick demo:
Doing a GET on the url http://www.mocky.io/v2/5583c7fe2dda051e04bc699a will return the following json:
{
data: [ ]
}
If you run the following class, you'll see it works just fine:
public class RetrofitDemo {
interface API {
#GET("/5583c7fe2dda051e04bc699a")
void getDataList(Callback<DataList> cb);
}
static class DataList {
List<Data> data;
}
static class Data {
}
public static void main(String[] args) {
API api = new RestAdapter.Builder()
.setEndpoint("http://www.mocky.io/v2")
.build()
.create(API.class);
api.getDataList(new Callback<DataList>() {
#Override
public void success(DataList dataList, Response response) {
System.out.println("dataList=" + dataList);
}
#Override
public void failure(RetrofitError retrofitError) {
throw retrofitError;
}
});
}
}
Your issue is your java model doesn't reflect the data it's trying to deserialize to.
//{"data":[]} does not map to List<Data> data.
// If the server was just returning an array only then it would work.
// It will match to the entity below make sure your cb = Callback<MyItem>
public class MyItem {
List<Data> data;
}