Realm - Inner realm list object is not updated after copyToRealmOrUpdate() - android

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);

Related

Why recyclerview adapter is not setting data properly?

Few days ago, I asked this question for avoiding repetition of reycylerview items whose accepted answer helped me to avoid data repetition.
But now I am facing new problems like: not showing all items sometimes only one like this . The real problem is even after getting all items from server properly data is not shown in recyclerView properly .Data are skipped randomly. I don't understand where the problem is. I even tried to use for loop instead of foreach but result was not different. Can anyone please help to fix this? It has been pain in the neck from last one week.
Code:
private List<TimelineData> timelineDataList=new ArrayList<>() ;
public void onCreateView(){
recyclerview.setLayoutManager(new LinearLayoutManager(ctx));
//Setting Adapter
adapter=new CustomRecyclerViewAdapter(timelineDataList);
recyclerview.setAdapter(adapter);
}
#Override
public void onStart() {
super.onStart();
// Fetching data from server
socket.disconnect();
socket.connect();
//Getting Data from server
JSONObject obj=new JSONObject();
try {
obj.put("timeline_posts","all");
socket.emit("data",obj);
} catch (JSONException e) {
e.printStackTrace();
}
}
void addTimelineData(String type,String time,String img_link){
boolean isRepeated = false;
for(TimelineData data : timelineDataList){
if(data.getTime().equals(time)){
isRepeated = true;
}
}
if(!isRepeated){
timelineDataList.add(new TimelineData(type,time,img_link));
}
adapter.notifyDataSetChanged();
}
private Emitter.Listener handlePosts = new Emitter.Listener(){
#Override
public void call(final Object... args){
try {
JSONArray jsonArray=(JSONArray)args[0];
timelineDataList.clear(); //clear data before inserting new one
for(int i=0;i<jsonArray.length();i++){
try {
JSONObject ob=jsonArray.getJSONObject(i);
post_type=ob.getString("post_type");
post_time=ob.getString("time");
post_link=ob.getString("img_link");
addTimelineData(post_type,post_time,post_link);
} catch (JSONException e) {
e.printStackTrace();
}
}
} catch (Exception e) {
Log.e("error",e.toString());
}
}
};
Adapter Code:
#Override
public void onBindViewHolder( CustomRecyclerViewHolder holder, int position) {
//Fetching TimelineData
TimelineData timelineData=totalList.get(position);
///Here I'm getting and converting array of image links which are there in jsonObject to arraylist
//Getting Imglink
Gson gson=new Gson();
Type type = new TypeToken<ArrayList<String>>() {}.getType();
ArrayList<String> arrayList = gson.fromJson(timelineData.getImg_link(), type);
//Setting ViewPager
CustomPagerAdapter adp=new CustomPagerAdapter(arrayList);
pager.setAdapter(new PagerAdapter() {
#Override
public int getCount() {
return 0;
}
#Override
public boolean isViewFromObject(View view, Object object) {
return false;
}
});
holder.pager.setCurrentItem(position, false);
holder.pager.clearAnimation();
adp.notifyDataSetChanged();
holder.pager.setAdapter(adp);
holder.pager.setOffscreenPageLimit(1);
}
You are using notifyDataSetChnaged() very quickly, change notify method by this. So that you notify only selected item which you inserted.
Recommended method : You will put below code in your adapter and call this method from for loop where you were setting notifyDataSetChnaged here String s will be replaced by your model class. By this way you just notify only one element when inserting one element. This will also create some inserting animation automatically.
public void insertItemInList(String s) {
if (list == null) list = new ArrayList<>();
list.add(s);
notifyItemInserted(list.size() - 1);
}
Or
You can call notify outside for loop when your work is done like this.
for(int i=0;i<jsonArray.length();i++) {
try {
JSONObject ob=jsonArray.getJSONObject(i);
post_type=ob.getString("post_type");
post_time=ob.getString("time");
post_link=ob.getString("img_link");
addTimelineData(post_type,post_time,post_link);
} catch (JSONException e) {
e.printStackTrace();
}
}
adapter.notifyDataSetChanged();
Issue is that you are notifying adapter rapidly, it can also lead to UI inconsistency.
Let me know if this resolves your issue.
You are modifying the data list in the non-UI thread which could cause problems in the RecyclerView. Instead, you should collect all the data in tempTimelineDataList at once and update the adapted timelineDataList in the UI thread.

How to avoid data repetition in RecyclerView - Android?

I'm developing a wallpaper app using mongodb. I'm retrieving data from database and displaying it on my recyclerView by the help of a data-model class without any problem. Also I'm using swipe refresh layout to allow user for refreshing the recyclerView for new data.
But now the problem is how can I avoid data repetition and show only new posts to the user. I meant if there are 5 pics are there in my db in my first query I'll get those 5 so when the user will refresh the layout again the recyclerView's item is increased to 10 and I wanna avoid this I want to show them new pics only when the posts in db will be increased to 6 or more.
I think this data avoid concept is also used in social media apps. but for this context I wonder what I have to do?
Data model class:
public class TimelineData {
private String type, time, img_link;
public TimelineData(String type, String time, String img_link) {
this.type = type;//type means what type of wallpaper
this.time = time;
this.img_link = img_link;
}
public String getType() {
return type;
}
public String getTime() {
return time;
}
public String getImg_link() {
return img_link;
}
}
Adding Data to recyclerview:
private List<TimelineData> timelineDataList = new ArrayList<>();
public void onCreateView() {
recyclerview.setItemViewCacheSize(20);
recyclerview.setDrawingCacheEnabled(true);
recyclerview.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH);
recyclerview.setLayoutManager(new LinearLayoutManager(ctx));
//Setting Adapter
adapter=new CustomRecyclerViewAdapter(timelineDataList);
recyclerview.setAdapter(adapter);
}
#Override
public void onStart() {
super.onStart();
// Fetching data from server
socket.disconnect();
socket.connect();
//Getting Data from server
JSONObject obj=new JSONObject();
try {
obj.put("timeline_posts","all");
socket.emit("data",obj);
} catch (JSONException e) {
e.printStackTrace();
}
}
void addTimelineData(String type,String time,String img_link) {
timelineDataList.add(new TimelineData(type,time,img_link));
adapter.notifyDataSetChanged();
}
private Emitter.Listener handlePosts = new Emitter.Listener() {
#Override
public void call(final Object... args){
try {
JSONArray jsonArray=(JSONArray)args[0];
for(int i=0;i<jsonArray.length();i++){
try {
JSONObject ob=jsonArray.getJSONObject(i);
post_type=ob.getString("post_type");
post_time=ob.getString("time");
post_link=ob.getString("img_link");
addTimelineData(post_type,post_time,post_link);
} catch (JSONException e) {
e.printStackTrace();
}
}
} catch (Exception e) {
Log.e("error",e.toString());
}
}
};
You can try cleaning the data source whenever you get new data, that way you'll always reinsert the complete dataset, if you have new data it will be inserted with the old one and you don't have to worry about repeated data in the mobile app, only in the server.
private List<TimelineData> timelineDataList=new ArrayList<>() ;
public void onCreateView(){
recyclerview.setLayoutManager(new LinearLayoutManager(ctx));
//Setting Adapter
adapter=new CustomRecyclerViewAdapter(timelineDataList);
recyclerview.setAdapter(adapter);
}
#Override
public void onStart() {
super.onStart();
// Fetching data from server
socket.disconnect();
socket.connect();
//Getting Data from server
JSONObject obj=new JSONObject();
try {
obj.put("timeline_posts","all");
socket.emit("data",obj);
} catch (JSONException e) {
e.printStackTrace();
}
}
void addTimelineData(String type,String time,String img_link){
boolean isRepeated = false;
for(TimelineData data : timelineDataList){
if(data.getTime().equals(time)){
isRepeated = true;
}
}
if(!isRepeated){
timelineDataList.add(new TimelineData(type,time,img_link));
}
adapter.notifyDataSetChanged();
}
private Emitter.Listener handlePosts = new Emitter.Listener(){
#Override
public void call(final Object... args){
try {
JSONArray jsonArray=(JSONArray)args[0];
timelineDataList.clear(); //clear data before inserting new one
for(int i=0;i<jsonArray.length();i++){
try {
JSONObject ob=jsonArray.getJSONObject(i);
post_type=ob.getString("post_type");
post_time=ob.getString("time");
post_link=ob.getString("img_link");
addTimelineData(post_type,post_time,post_link);
} catch (JSONException e) {
e.printStackTrace();
}
}
} catch (Exception e) {
Log.e("error",e.toString());
}
}
};
before you add new elements to the wallpaper list, check to see if an object with that id exist in the list. if it does, skip it, else add it.

Realm transaction using from different thread

I have writeToRealm method that I use very often. And I need to use it from different threads and cut boilerplate code. What is the better way for this task?
private boolean writeToRealm(String user, String id) {
SubscriberObject subscriber = new SubscriberObject();
if(mRealm.where(SubscriberObject.class)
.equalTo(SubscriberObject.ID,id).findAll().isEmpty()
&&mRealm.where(SubscriberObject.class)
.equalTo(SubscriberObject.USERNAME,user).findAll().isEmpty()) {
subscriber.setId(id);
subscriber.setUsername(user);
mRealm.beginTransaction();
mRealm.insert(subscriber);
mRealm.commitTransaction();
return true;
}
return false;
}
I am planning to use construction below (or something like this) but I can't create a correct construction:
public static Boolean writeToRealm(final String user,final String id){
Realm mRealm;
return Flowable.using(
mRealm = Realm.getDefaultInstance(),
new Function<Realm, Boolean>() {
#Override
public Boolean apply(#NonNull Realm realm) throws Exception {
SubscriberObject subscriber = new SubscriberObject();
if(realm.where(SubscriberObject.class)
.equalTo(SubscriberObject.ID,id).findAll().isEmpty()
&&realm.where(SubscriberObject.class)
.equalTo(SubscriberObject.USERNAME,user).findAll().isEmpty()) {
subscriber.setId(id);
subscriber.setUsername(user);
realm.beginTransaction();
realm.insert(subscriber);
realm.commitTransaction();
return true;
}
return false;
}
},
mRealm.close()).subscribeOn(Schedulers.io());
}
Or may be I need to create a thread class with looper for this task?
How to better integrate this method and similar methods into a clean architecture?
I think you're just looking for
private boolean writeToRealm(String user, String id) {
try(Realm realm = Realm.getDefaultInstance()) {
if(realm.where(SubscriberObject.class).equalTo(SubscriberObject.ID,id).count() <= 0L
&& realm.where(SubscriberObject.class).equalTo(SubscriberObject.USERNAME,user).count() <= 0L) {
final SubscriberObject subscriber = new SubscriberObject();
subscriber.setId(id);
subscriber.setUsername(user);
realm.executeTransaction(r -> r.insert(subscriber));
return true;
}
}
return false;
}
I think this could be way an easier solution:
public static Boolean writeToRealm(String user, String id) {
Realm realm = Realm.getDefaultInstance();
SubscriberObject subscriber = new SubscriberObject();
if (realm.where(SubscriberObject.class).equalTo("ID", id).or().equalTo("USERNAME", user).findAll().isEmpty()){
subscriber.setId(id);
subscriber.setUsername(user);
realm.beginTransaction();
realm.insert(subscriber);
realm.commitTransaction();
realm.close();
return true;
}
realm.close();
return false;
}
If you need some explainations, just tell me and I will implement it :)
PS: if I missunderstood your question, let me know!
The asynchronous transaction support works the same way as the current executeTransaction, but instead of opening a Realm on the same thread, it will give you a background Realm opened on a different thread. You can also register a callback if you wish to be notified when the transaction completes or fails.
realm.executeTransactionAsync(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
Dog dog = realm.where(Dog.class).equalTo("age", 1).findFirst();
dog.setName("Fido");
}
}, new Realm.Transaction.OnSuccess() {
#Override
public void onSuccess() {
Log.d("REALM", "All done updating.");
Log.d("BG", t.getName());
}
}, new Realm.Transaction.OnError() {
#Override
public void onError(Throwable error) {
// transaction is automatically rolled-back, do any cleanup here
}
});
read more
First of all you can't have boolean return type if you want to do your transaction asynchronously. You will have to use either Interface pass result back to caller or you have to opt for some other means like RxJava.
just to give you example.
The RxJava way(As this would be simplest way):
public static Flowable<Boolean> writeToRealm(final String user,final String id) {
return Flowable.fromCallable(
new Callable<Boolean>() {
#Override
public Boolean call() throws Exception {
Realm realm = Realm.getDefaultInstance();
if(realm.where(SubscriberObject.class)
.equalTo(SubscriberObject.ID,id).findAll().isEmpty()
&&realm.where(SubscriberObject.class)
.equalTo(SubscriberObject.USERNAME,user).findAll().isEmpty()) {
SubscriberObject subscriber = new SubscriberObject();
subscriber.setId(id);
subscriber.setUsername(user);
realm.beginTransaction();
realm.insert(subscriber);
realm.commitTransaction();
mRealm.close();
return true;
}
mRealm.close();
return false;
}
});
}
You subscribe the returned Flowable on desired thread/schedular to perform transaction on that particular thread.

how to do a PUT using retrofit with a JSONObject that contains a JSONArray

I'm trying to shift over from volley to retrofit and I don't fully understand how to do a PUT with a JSONObject that contains a JSONArray.
The JSONObject body that I want to send to the server should look like this:
{
“account”: [
{“availability”: “offline”}
]}
here is my pojo
public class AvailabilityModel {
JSONObject account;
public AvailabilityModel(JSONObject account) {
this.account = account;
}
}
and my interface
public interface AvailabilityAPI {
#Headers( "Content-Type: application/json" )
#PUT(PATH)
Call<AccountParentModel> setAvailability(#Path("parameter") String accountId, #Body AvailabilityModel object);
class Factory {
private static AvailabilityAPI service;
public static AvailabilityAPI getInstance() {
if (service == null) {
Retrofit retrofit = new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl(App.BASE_URL)
.build();
return service = retrofit.create(AvailabilityAPI.class);
} else {
return service;
}
}
}
}
and finally, In my activity I do this:
JSONObject account = new JSONObject();
JSONArray array = new JSONArray();
JSONObject obj = new JSONObject();
try {
obj.put("availability", "offline");
} catch (JSONException e) {
e.printStackTrace();
}
array.put(obj);
try {
cloudNumber.put("account", array);
} catch (JSONException e) {
e.printStackTrace();
}
Log.e(this.getClass().getSimpleName(), "JSONObj sent to the server is: " + account);
AvailabilityModel availabilityModel = new AvailabilityModel(account);
AvailabilityAPI.Factory.getInstance().setAvailability(accountId, availabilityModel).enqueue(new Callback<AccountParentModel>() {
#Override
public void onResponse(Call<CloudNumberParentModel> call, Response<CloudNumberParentModel> response) {
Log.e("HomeActivity", "Success: availability = " + response.body().cloudNumbers.get(0).getAvailability());
}
#Override
public void onFailure(Call<CloudNumberParentModel> call, Throwable t) {
Log.e(this.getClass().getSimpleName(), " No good bro " + t.getMessage());
}
});
the problem with this is the server receives it in this format:
{“nameValuePairs”:{“account”:{“values”:[{“nameValuePairs”:{“availability”:“available”}}]}}}
Any help will be much appreciated.
Dont do like that..
let me give some brief to simply understand you about that.
step 1. take your json reqest .
In your case
{ “account”: [ {“availability”: “offline”} ]}
Step 2 . make model class.
That i describe at here Link
So in your case your model class is.
public class AvailabilityModel {
private List<AccountBean> account;
public List<AccountBean> getAccount() {
return account;
}
public void setAccount(List<AccountBean> account) {
this.account = account;
}
public static class AccountBean {
/**
* availability : offline
*/
private String availability;
public String getAvailability() {
return availability;
}
public void setAvailability(String availability) {
this.availability = availability;
}
}
}
Step 3 : putting value inside model class
first
AccountBean account = new AccountBean();
account.setAvailability("offline");
now take one array
List<AccountBean>list = new List<AccountBean>();
list.add(account);
so above is your list of account. now one step to complete make model.
AvailabilityModel model =new AvailabilityModel();
model. setAccount(list);
Happy coding :)

How to load data from local realm db?

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

Categories

Resources