Best way to keep actual data in application - android

I Faced some problem with the relevance of the data in application.
In our app we have 1 activity and many fragments.
For example, we have 1 Fragment with list of User and there button for like(with 3 states: none/like/favorite).
In next page we have full description of User and like button too.
If we press like button in userList, there will be no problem and in details screen we see correct like state. But if we click like button in User details and go back to list, there still past data here.
We can't use activityForResult because fragment.
We using rx, maybe there some way to easy resolve this problem?

In order to reuse the Fragment UI components, you should build each as a completely self-contained, modular component that defines its own layout and behavior. Each Fragment then can communicate data to the Activity, and the Activity can also send data to Fragments.
In your case, the Activity can hold the actual data. When user modifies something in the details Fragment, the Fragment should intimate that to the Activity. The Activity, then in turn should inform the list Fragment.

Actually, you can use the onActivityResult() approach with fragments. I've seen this pattern a couple of times in different projects, up to you to decide whether it suits you.
Assuming your left fragment is AFragment and your right fragment is BFragment, here's the idea.
(1) In AFragment, set a target fragment prior to commiting a transaction from A to B:
Fragment bFragment = new BFragment();
bFragment.setTargetFragment(this, 42); // `this` is your AFragment instance
// transact here
(2) Provide onActivityResult():
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
// your logic here
}
(3) In BFragment, prepare your data in onDetach() and call onActivityResult() on your target.
#Override
public void onDetach() {
super.onDetach();
if (getTargetFragment() != null) {
Intent data = new Intent();
// pass your data here
getTargetFragment().onActivityResult(getTargetRequestCode(), Activity.RESULT_OK, data);
}
}
This way, whenever BFragment pops back, your AFragment has a chance to handle its data.

The way I deal with this situation in my app is to use a central "repository" of SharedPreferences to store serialized data across multiple screens. Then every time a new Activity (or in your case, Fragment) gets created or resumed, it reaches into the serialized data repo to populate its shared data.
This is how I would imagine this would work in your case. To use your "user" example, I would create a SharedPreferences file to store my list of user data objects. So I would have a class that stores everything I need to know about a user -- their info, their "like" status, etc.
Assuming I have the users in a List somewhere, I would just serialize that with Gson, and save the serialized data into the SharedPreferences I created. Then, each Fragment can just poll the serialized data to get the current state of the user. If some changes are made, each Fragment would save the data back to the same SharedPreferences file so that other Fragments could then use it to instantiate their views.
Here's some pseudo code...
//Some kind of data class for User
class MyUser {
String name;
String location;
String likeStatus;
//... whatever else
}
Then, assuming you have a List of users somewhere, I would serialize that List and store it in a global SharedPreferences file like this...
//ArrayList<MyUser> users <- defined somewhere
Gson gson = new Gson();
String usersJson = gson.toJson(users);
SharedPreferences userPrefs = getSharedPreferences("users_storage", MODE_PRIVATE);
SharedPreferences.Editor editor = userPrefs.edit();
editor.putString("users_data_key", usersJson);
editor.apply();
Then when a new Fragment is loaded and it needs to know the current state of the users, it simply opens the SharedPreferences file and gets the current state of everything like this...
SharedPreferences userPrefs = getSharedPreferences("users_storage", MODE_PRIVATE);
String usersJson = userPrefs.getString("users_data_key", null);
Type collectionType = new TypeToken<ArrayList<MyUser>>(){}.getType();
ArrayList<MyUser> users = gson.fromJson(usersJson, collectionType);
Just to be clear, this approach would be all done in your Activity, and when you were starting a new Fragment, you would simply pass in the data it needs to instantiate correctly. Then, if a Fragment needs to save the data, it would simply call a method of your Activity and pass it the data that needs to be saved, and your Activity would execute the code above to save it.

Related

Passing data from MainActivity to sub-Activity when sub-Activity is opened through an Adapter?

So my title may be hard to follow, but I'll try to clarify and expand on what my issue is below.
I currently have an app that starts its life as a MainActivity with multiple Fragments sitting in a ViewPager.
In the MainActivity, I have Android In-App Billing V3 (library) setup so that the user can pay to remove ads. This works just fine in the MainActivity but my issue arises when moving to another Activity.
The first Fragment the user is presented with upon launching the app, contains a RecyclerView with an ArrayList of items. To get to a sub-Activity from the MainActivity, the user presses a button on one of the items in the RecyclerView, which means that the Intent data used to change Activities is contained within the RecyclerViewAdapter.
My issue is that once my app knows that the user has paid to remove ads, I want the app to also remove ads in all sub-Activities as well.
I don't know how to pass this info (that the "Remove Ads" in-app has been purchased) from Activity -> sub-Activity, when sub-Activity is launched through the RVAdapter instead.
So my question is: How would I pass data from MainActivity -> RVAdapter -> Sub-Activity?
Or is there an even better, more efficient way of passing this data along without using Intents? Do let me know!
Did my description of the issue make sense? I hope so! Otherwise let me know how I might clarify it! If you need me to paste in any code, let me know as well.
Thanks for any of your help!
you can use EventBus (greenrobot) nice library for send event this linke
to send evnts
after add library put below method to your main activity:
#Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(MessageEvent event)
don't forget about Register and unregister subscriber, do it like this :
#Override
public void onStart() {
super.onStart();
EventBus.getDefault().register(this);
}
#Override
public void onStop() {
super.onStop();
EventBus.getDefault().unregister(this);
}
finally post your event from everywhere like your subactivity :
EventBus.getDefault().postSticky(new MessageEvent());
Notice:I add postSticky(); to cache data on memory ,Then the sticky event can be delivered to subscribers or queried explicitly.
better solution
but i think you can save value in Sharedpreferences after purcahse:
SharedPreferences.Editor editor = getSharedPreferences(MY_PREFS_NAME,
MODE_PRIVATE).edit();
editor.putBoolean("pay", true);
editor.apply();
then check this valu every Activity on onCreat method
to show adds or don't
SharedPreferences prefs = getSharedPreferences(MY_PREFS_NAME, MODE_PRIVATE);
pay = prefs.getBoolean("pay", false);
if (pay) {
show();
}else dontShow();

Opening Instance of Activity

I have an app that hold post information in an activity. in this activity related posts listed in bottom of post. User by clicking on related post can go to post activity and see that post info and related posts too.
As you can see in image, I have Activity A that holds post and it's related posts. When user Click on post I send user to Activity A with new post id and fill activity by new data.
But I think this is not Right way!
should I used Fragment instead of Activity?
Opening another Instance of an Activity on top of another is simplest way of navigating a content graph. User can simply press back, and go to previously opened content, until user reaches back to starting Activity, then the application closes. Though pretty straight forward, this particular approach has two issues:
It may happen that a lot of Instances of same activity are on the stack, utilising a large amount of device resources like memory.
You don't have a fine grained control over Activity Stack. You can only launch more activities, finish some, or have to resort to intent flags like FLAG_CLEAR_TOP etc.
There is another approach, that re-uses the same Activity instance, loads new content in it while also remembering the history of content that was loaded. Much like a web browser does with web page urls.
The Idea is to keep a Stack of content viewed so far. Loading new content pushes more data to stack, while going back pops the top content from stack, until it is empty. The Activity UI always displays the content from top of the stack.
Rough Example:
public class PostActivity extends AppCompatActivity {
// keep history of viewed posts, with current post at top
private final Stack<Post> navStack = new Stack<>();
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// get starting link from intent extras and call loadPost(link)
}
private void loadPost(String link){
// Load post data in background and then call onPostLoaded(post)
// this is also called when user clicks on a related post link
}
private void onPostLoaded(Post post){
// add new post to stack
navStack.push(post);
// refresh UI
updateDisplay();
}
private void updateDisplay(){
// take the top Post, without removing it from stack
final Post post = navStack.peek();
// Display this post data in UI
}
#Override
public void onBackPressed() {
// pop the top item
navStack.pop();
if(navStack.isEmpty()) {
// no more items in history, should finish
super.onBackPressed();
}else {
// refresh UI with the item that is now on top of stack
updateDisplay();
}
}
#Override
protected void onDestroy() {
super.onDestroy();
// cancel any background post load, release resources
}
}
I would choose:
activity/fragment depends on complexity with:
horizontal recyclerview with custom expanded card view
and inside this expanded card view second vertical recyclerview :)
Here's what you can try.
Create a PostActivity which is a shell for fragments. Inside this activity you can just replace fragments using FragmentTransaction.
Your PostActivity can now have a PostFragment which will hold post and related posts. Now on click of post you can replace PostFragment with PostDetailFragment with postID being sent to the new fragment as a bundle. The PostDetailFragment will now display details according to id.
Check here: http://www.vogella.com/tutorials/Android/article.html#components_fragments
By seeing the picture the way i would implement is i would have create an activity with a bottom listview for your items and on top there would be a framelayout for holding fragments . when user click on any list item i would load the respective fragment in the activity
It all depends on what you are trying to achieve. What would you expect to happen when the user touches the back button after going down a couple of levels? If you want to the application to exit, no matter how deep in the sequence they have gone, then the best solution in my opinion is to simply reload the same activity with the new data and invaliding the affected views. If you need the back button to take the user back to the previous data, then the next question would be if you are keeping track of the past data breadcrumb. If so, then just intercept the back button and load the previous data for as long as there is data in your stack, or exit if you get to the top. If you don't want to keep track of the previous data chain, then instead of loading one activity with the new data, you can start a new activity of the same class, but with the new data. Android with keep the track of activities and each back button touch would close the running activity and take the user to the previous activity. Choice of activity versus fragment is just yours. You can use fragments that hold the data that you want to change after each user touch, create new ones when needed, disconnect the previous ones, and connect the new ones. You will need to do some extra work to make sure the back button works correctly (depending on you want the back button to behave). Based on what I can tell, it is simpler to just have one activity and load new data when needed and keep a trail of data changes, if you need to be able to go back.
It can be achieved using activity alone. Though I preferred moving all related UI to fragment.
You can use Navigator class.
Here the steps:
1. Add Navigator Class
public class Navigator {
private static Navigator instance;
public synchronized static Navigator getInstance() {
if (instance == null) {
instance = new Navigator();
}
return instance;
}
public void navigateToActivityA(Context context) {
Intent activity= AActivity.getCallingIntent(context);
context.startActivity(activity);
}
}
2. Add the calling method to your Activity class.
public static Intent getCallingIntent(Context context) {
return new Intent(context, AActivity.class);
}
3. Call the activity with the following code in your caller activity.
Navigator.getInstance().navigateToActivityA(this);
I suggest that you read about AndroidCleanArchitecture
For this task...
0) Starting new activity
I read again about question, and understood that you need advice for starting activity. So, starting new activity it's Ok, your main problem will be with another things (see below).
But lets talk about starting another data. Using Fragment instead doesn't resolve your task, fragments helps with different screen work. Using for example just data refreshing as a variant. You may use just single activity and refresh only data, it will look much better, if you also add animation, but not better than starting activity.
Using Fragment helps your with different screen actions. And maybe, answering on your question - it will be most suitable solution. You just use single acitivity - PostActivity, and several fragments - FragmentMainPost, FragmentRelated - which will be replaced, each other, by selecting from related post.
1) Issues with returning back
Lets imagine, that users clicks to new one activity and we loaded new data. It's Ok, and when Users clicks over 100 activities and receiving a lot of information. It's Ok, too. But main question here it's returning back (also another about caching, but lets leave it, for now).
So everyone know, it's bad idea to save a lot of activities in stack. So for my every application, with similar behavior we override onBackPressed in this activity. But how, lets see the flow below:
//Activities most have some unique ID for saving, for ex, post number.
//Users clicks to 100 new activities, just start as new activity, and
//finish previous, via method, or setting parameter for activity in AndroidManifest
<activity
noHistory=true>
</activity>
....
//When activity loaded, save it's activity data, for ex, number of post
//in some special place, for example to our Application. So as a result
//we load new activity and save information about it to list
....
// User now want return back. We don't save all stack this activities,
// so all is Ok. When User pressing back, we start action for loading
//activities, saved on our list..
.....
onBackPressed () {
//Getting unique list
LinkedTreeSet<PostID> postList =
getMyApplication().getListOfHistory();
//Getting previous Post ID based on current
PostID previousPostID = postList.get(getCurrentPost());
//Start new activity with parameter (just for ex)
startActivity(new Intent().putExtra(previousPostID));
}
RESULT
I found this as the best solution for this tasks. Because in every time - we work only with single activity!

How to share cursor between fragments/activities

I'm trying to build an application that will have list/detail panes built with fragments. List is created from the ContentProvider. When clicked on the list the details is populated or activity created. In action similar to gmail application. How the data should be shared/passed between fragments/activities?
Population of the list wasn't that hard, but how can I pass the selection to the details fragment (either in the same activity or another activity)? Should I query again and returned result used in details?
It needs to be like gmail app, so swipe left/right should then change the details accordingly to the same order as the list is either in the single pane layout or dual pane layout.
I think for this I need to share the Cursor returned by the CursorLoader to keep the same order. Then swipes would increase/decrease the index and would display correct item.
Also as I already loaded data, I wanted to reuse it without new queries.
Could someone point me to the right direction, what you would you do to achieve that (no code but algorithm/steps)?
At the moment I have 2 activities list and detail, the list has dual and single panel layout and list fragment with details fragment are used, detail has just single pane with details fragment. I think I could reduce it to be a single activity and juggle the fragments but don't know if it will be good.
Here is a way to pass data from one activity to another :
intent = new Intent(this, ProductListActivity.class);
Bundle bundle = new Bundle();
bundle.putSerializable(PRODUCT_LIST, productList);
bundle.putString(KEY_WORD, keyWord);
intent.putExtras(bundle);
startActivity(intent);
If you're in an activity and want to pass data to a fragment in that activity, just use setters from that fragment
EDIT : Since last comment, implement a class to handle your object with the Serializable interface :
public class MyDBObject implements Serializable {
//Stuff
}
Then when you fetch from your DB, return or a MyDBObject, or a List<MyDBObject>
Finally, when you need to pass the data, just use
Intent intent = new Intent(SourceActivity.this, TargetActivity.class);
intent.putExtra("DB_OBJECTS", ArrayList<MyDBObject>mDBObject); // For a list
intent.putExtra("DB_OBJECT", mDBOject); //For a single object
What I did then was:
activity holding the fragment(s) will do:
implement LoaderManager.LoaderCallbacks<Cursor>
call the getSupportLoaderManager().initLoader(ALL_DATA_LOADER_ID, null, this);
pass arguments of the LoaderCallbacks to the fragments that also implements LoaderCallbacks
call the getSupportLoaderManager().restartLoader(ALL_DATA_LOADER_ID, null,
this); whenever data was changed outside of the ContentProvider means
This way I'm sharing the same cursor between fragments. Between activities I'm exchanging (via Intent) the data needed to request the same dataset and selection id if needed.

Android : Get previous fragment name to do nothing in onCreate

I have a detected a bug in my app today : When a user clic on an item in my listview, it launch a new fragment with details about this item. But when the user is in this fragment, if he change orientation of the device, the onCreate method of my first class (which fetch my listview) is called, and the app crashed.
I would like to know if it's possible to get the name of the previous fragment, in order to add a test like the following in my onCreate method :
if (!fragmentName.equals("common")){
Log.d("INFO", "Do nothing in this case, because user was in detail fragment before !"),
} else {
super.onCreate(savedInstanceState);
refreshList(true);
}
I don't think it is possible to get the name of the previous fragment as its creating a new instance of your Fragment object.
If I am right in what you are trying to do you need to somehow store the object for which you are displaying data so that when the Fragment is recreated you can then retrieve the required object. One way is you keep the ID or a unique property of the object in SharedPreferences and then reacquire its data when it reloads from a singleton somewhere.
Basically you may need to change the way you retrieve your data or pass it from the original opening Fragment so that you can retrieve it again if the second Fragment is destroyed.

Proper way to give initial data to fragments?

So I've learned that I need an empty constructor in order for my fragments not to crash on reinitialization. My problem is that I use lists of data for my fragments when they are initialized (at least some of them). So what would be a good way to start new fragments with a list of data. Should I in the OnCreate() make a getData method which gets the data from some other source or what would be a proper approach?
Feeding the bundle with data really wouldn't be a very good approach as I have a lot of data.
So let's take a case (I understand it tons better that way).
When a user clicks on a button the fragment is started. What I used to do was creating a new fragment this way:
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.center_container, new DetailFragment(item));
fragmentTransaction.addToBackStack(DETAIL_TAG);
fragmentTransaction.commit();
Then in my fragment:
public DetailFragment(EventItem item) {
mItem = item;
mPlaces = Database.getContainerPlaces(getActivity()).getValidItems();
}
I can't give all the data to a bundle, so that wouldn't work. So what should I do?
A: Should I initialize the fragment with the empty constructor and then from my activity use setters to set the data directly in the fragment? However, won't I be missing data if the user presses home, Android closes the fragment and the user later returns?
B: Should I initialize the fragment with factory pattern and call setRetainInstance(true), give the fragment a key for identifying the data, and then letting the fragment fetch the data needed in onCreateView from some third source?
C: Should I just make an empty constructor and then in onCreate() fetch the data needed for the fragment?
It should be noted that the app is locked in portrait so the issue is primarily with maintaining the objects when Android closes and the user restarts.
So what would be a good way to start new fragments with a list of data.
Use the factory pattern and the "arguments" Bundle, such as:
package com.commonsware.empublite;
import android.os.Bundle;
public class SimpleContentFragment extends AbstractContentFragment {
private static final String KEY_FILE="file";
protected static SimpleContentFragment newInstance(String file) {
SimpleContentFragment f=new SimpleContentFragment();
Bundle args=new Bundle();
args.putString(KEY_FILE, file);
f.setArguments(args);
return(f);
}
#Override
String getPage() {
return(getArguments().getString(KEY_FILE));
}
}
If you are retaining your fragment instance, you should be able to get away with just using ordinary setters to put stuff in data members. The "arguments" Bundle is retained as part of configuration changes, so for non-retained instances, this is the way to ensure your setup data is retained if the user rotates the screen, etc.

Categories

Resources