I'm an early and very happy adopter of both Flux and React so much so that, recently, I ported Fluxxor into Android and it's been okay so far.
The issue I am having with it Flux is dealing with data for a Single Item or Details Page. Bear with me below. I will try to be as clear as I can.
The pattern I am using is.
On page load(componentWillMount/componentWillReceiveProps and onStart), I check if the id passed to the page (via url or bundle) matches the id of the item currently in the store and the page if the store is in a processing or success state.
If yes, I do nothing, else, I dispatch an action to load the data for that item.
componentWillMount: function () {
id = this.props.params.path.split("-")[0];
var artistData = this.props.state.artistData;
if(artistData.id != id)
this.getFlux().actions.artistActions.loadArtist(id);
else if (!artistData.artist && !artistData.loading)
this.getFlux().actions.artistActions.loadArtist(id);
this.getFlux().actions.userActions.fetchSuggestions();
}
protected void onStart() {
GenreSongsStore.State state = App.getFlux().getStore(GenreSongsStore.class).getState();
if(mId == state.Genre.getId()) {
if (state.HasMore)
App.getFlux().getActions().Genres.songs(mId, state.Page + 1);
}
else
App.getFlux().getActions().Genres.songs(mId, 1);
super.onStart();
}
In React this is fine since you use a single state on the root. I didn't bother too much until I started working with Android.
Here, I don't use a single state but query the relevant store and it totally smells
If you are not using that page, the data is still held in memory
Since the data is not shared it seems there is no benefit to doing it like this
Won't it simply be easier to load the data in the component/activity/fragment?
However, I get the benefit of maintaining the currently loading state. So the user can minimize and reopen the app and we continue (no need for saving an instance bundle).
I know by doing it like this, I lose the benefit of unidirectional data flow. But it seems to make more sense in the context of Android (pun intended).
Can I have your views on how you do this and if I'm simply worried about nothing.
NB: The data is not shared by any other stores at.
Related
I am new to Epoxy and I'm currently trying some use-cases to check if it's a good fit for my project. I understand that the data that are set to a Controller should be immutable.
In my case I have a View with several toggles and checkboxes and I want to keep track of the user's interactions, because based on those interactions I need to create my network request, later on. I have searched a lot in the documentation and sample projects of Epoxy but haven't found an example with the proper way to do such a thing.
What is the correct way for the user's interaction to change the data model that my controller has.
After a lot of searching, it turns out that you should copy (shallow not deep) the list past to the controller, and change the data (interacted by the user) to the new one, and the pass it again to the controller. I just put this answer here for anyone that stumbles upon this post
val newList = ArrayList(originalList.map { it.copy() })
newList.find { it.id == event.data.id }?.isMainToggleOn = !event.data.isMainToggleOn
controller.setData(newList)
originalList = newList
My Android app has this flow of screens when launched:
Splash -> Chat Groups > Chat Screen (showing chat messages)
On Chat Screen I have my Custom RecyclerView Implemented.
On fresh launch (or after killing the app), I go to Chat Screen, it loads previous messages fine, and new incoming message is also seen when u r on this screen.
Now if I press Android's Back button few time to exit the app, and then relaunch the app and go to Chat Screen, previous messages appear fine BUT the new incoming message is not visible.
Important thing is, even if I don't go to Chat Screen the first time and close the app from Groups Screen, then relaunching and going to Chat Screen again causes the problem and I dont see new incoming Chat messages.
I have debugged it and all code is being executed fine. The incoming message is added to the list of RecycleView, and notifyDataSetChange() is being called, but onBindViewHolder() is not being called in this case, and that's why the list doesn't get updated.
The code is pretty lengthy, but if u still need to see it then I'll try to add.
This is driving me crazy, I am pretty sure it's a bug in Android.
If u can propose a workaround, like clearing the RecyclerView or Adapter somehow that it gets to same state as when i Kill the app and launch..
Here is the code:
//Initialize Recycler view
mMessageRecycler = findViewById(R.id.recyclerview_message_list)
mMessageRecycler?.layoutManager = LinearLayoutManager(this)
....
if (messagesAdapter == null) {
messagesAdapter = NewMessageListAdapter(this)
mMessageRecycler?.adapter = messagesAdapter
}
//Adapter
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position:Int) {
val message = messageList[position]
(holder as ReceivedMessageHolder).bind(message)
}
override fun getItemCount(): Int {
return messageList.size
}
...
//add new chat message. Breakpoint does hit this code
messageList.add(newMessage)
notifyDataSetChanged()
As it's impossible to tell what is going on without looking at a bigger picture, I'll give you a few pointers.
a. I suggest you attempt to use ListAdapter<T, K> as it forces you to provide a DiffUtil.ItemCallback implementation. This will allow you to avoid calling the expensive and extremely inefficient notifyDataSetChanged(); instead you will call adapter.submitList(...) and supply a List<T> with your data.
K is the ViewHolder type. Usually you use RecyclerView.ViewHolder (if I correctly recall or if you only have one viewHolder type then you can couple that there and use it directly. Otherwise you'll just have to "cast" your ViewHolders to be able to call their "bind" method.
b. As for "it doesn't work when I get back", this is a bit harder to detect, as we haven't seen how/where/when you fetch this data; are you using Android Coroutines? Are the list of messages stored in a repository relying on memory or database persistence? who updates this list?
As you can see, there are a few outstanding questions that we (the readers) cannot possibly infer given the information you've provided.
If you want to see the simplest example of a RecyclerView with ListAdapter, I often tend to link this sample I wrote because it shows how to put all pieces together.
c. You shouldn't need to do if (adapter == null) { // create it and set it } either. You can have:
class YourActivityOrFragment : ... {
private val adapter = YourAdapter()
override fun onViewCreated(...) {
yourRecyclerView.adapter = adapter
}
You can later set the data in the adapter once you have it, there's no need to delay the creation of this. If you're going to use a LinearLayoutManager, remember you can also set it directly in XML and avoid writing the code.
Finally I found the problem! It was due to some memory leak and threads issue.
At some point in the code i was re-initializing my Mqtt class, without checking if it is not null. So I just added a null check and it fixed
if (mqttMy == null) // added
mqttMy = MqttMy(context)
Screen 1: GridView
Screen 2: Detail Page
Task Achieve:
1) Load all the videos in gridview from the server.
2) User clicks at any position of gridview item.
3) Open and play the particular video in detail screen.
4) On vertical scroll play next or previous videos.
Current Implementation:
GridFragment {
ArrayList<VideoPostModel> videoPostList;
RecyclerView gridView;
onnItemClick() {
Intent intent = new Intent(this, DetailActivity.class);
intent.putExtra("data", videoPostList);
intent.putExtra("click_index", clickedIndex);
intent.putExtra("pagination_index", paginationIndex);
startActivity(intent);
}
}
DetailActivity {
VerticlaViewPager vertiCalViewPager;
ArrayList<VideoPostModel> videoPostList;
onCreate() {
videoPostList = getIntent().getParcelableArrayListExtra("data");
clickedIndex = getIntent().getIntExtra("clickindex", 0);
paginationIndex = getIntent().getIntExtra("pagination_index", 0);
VideoFragmentStatePagerAdapter viewPagerAdapter = new VideoFragmentStatePagerAdapter(videoPostList);
vertiCalViewPager.setAdapter(viewPagerAdapter);
}
}
Problem:
If videoPostList has more data(approx 100+ objects of VideoPostModel) while passing data from fragment to activity then app crashes, as there is a limitation of sending data with intent(https://stackoverflow.com/a/37054839/3598052).
Hacky Alternatives:
1) Static arraylist
2) Arraylist in Application class
Looking for the OPTIMAL and EFFICIENT solution to achieve above functionality.
Any suggestion, reference link or code in the direction of achieving this would be highly appreciated, and thanks in advance.
Update 1:
Another solution I found is passing data with enum, but as per comments I'm not sure about it's performance. Refrence: https://stackoverflow.com/a/14706456/3598052
I think you can write in an activity or use Arraylist in the application as you mentioned. Or it could be a library that recently appeared in the Android Jetpack. It is similar in nature to the Arraylist in application.
ViewModel objects that make it easier to manage and store data.
It lets you access data at different activities or fragments in an application. You try it and hope it will be useful to you
You have several options. I'll put my opinion here.
Static list
Enum
Singleton class with list
LiveData
Most easy would be making static list at activity or application level. Just make sure you are freeing up List memory after use by making it NULL.
Another solution I found is passing data with enum, but as per
comments I'm not sure about it's performance
There would be sure some differences in each of above approaches. But that would not be measurable difference, because each approach put List in memory, use it and then free up.
Looking for the OPTIMAL and EFFICIENT solution to achieve above
functionality.
Make static List, and make it NULL after use. will be most efficient and easy way.
You can make List NULL in onDestroy() of your Fragment.
You can use LiveData but I think it would be not a good idea to add LiveData library just for one use in app. Also you need to understand it first. So you can go with static list.
in Activity
showFragment();
ApplicationClass.list = myList;
In Fragment
onViewCreated(){
...
setAdapter(ApplicationClass.list);
...
}
onDestroy(){
ApplicationClass.list = null;
}
Important
It is never a good idea to pull all data at once from server. Please do pagination, which you app needs most, because there can be thousands of users online at one time.
So by that approach you will pass only few items to Fragment. then you will do pagination in Fragment.
This approach needs time to change flow a bit. But is most robust way in your case.
Edit
If you are already using pagination and still getting large data at one time, that's again an issue. Because pagination is used to escape these memory issues.
You can do 2 things as solution.
1. Ask for limited data at once, say 50-60 items per request.
2. You can map and remove unnecessary fields from your list when passing in intent.
I would prefer the first one.
I know I'm late but this will help some future visitor.
Add Pagination & transfer data with arraylist & clicked position to detail activity using intent & after, set the current position of clicked position.
like viewpager.setCurrentPosition(clickedPosition);
I'm using greenDAO in my android app to display a list of objects in a RecyclerView. I have a subclass of RecyclerView.Adapter that takes a list of objects which are greenDAO entities.
What I do in onCreate is:
Create an instance of my adapater passing null for my list. This is just to make the adapter known to the RecyclerView below.
Initialize the RecyclerView with layout and adapter.
Call a method that asynchronously queries the data using greenDAO and upon success updates the adapter with the actual list of objects so they are displayed.
This is the relevant code:
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
...
mLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
mListAdapter = new MyRecyclerAdapter(null);
mList.setHasFixedSize(true);
mList.setLayoutManager(mLayoutManager);
mList.setAdapter(mListAdapter);
refreshItems();
}
public void refreshItems()
{
AsyncSession asyncSession = ((App)getApplication()).getDaoSession().startAsyncSession();
asyncSession.setListenerMainThread(new AsyncOperationListener()
{
#Override
public void onAsyncOperationCompleted(final AsyncOperation operation)
{
if (operation.isCompletedSucessfully())
mListAdapter.setItems((List<Item>) operation.getResult());
}
});
asyncSession.loadAll(Item.class);
}
This works pretty well. Now I noticed, that of course the method that queries the database via greenDAO is invoked every time I rotate the activity or come back to it from another activity. That's pretty clear, since I'm calling that method from onCreate.
My question is: is it best practice to do this like I'm doing it (requery DAO every time) or should I make my objects parcelable and save the list I have in onSaveInstanceState and restore it in onRestore instead of requerying DAO?
What you're doing is completely valid and you don't need to save the queried data in onSaveInstanceState(), use in-memory cache, or any other optimization (even if GreenDAO wouldn't have internal cache).
In fact, you're more than all-right because you perform the query asynchronously - GreenDAO's creators kind of claim that the queries can be executed on UI thread in most cases (which I find hard to agree with).
I would also suggest that you perform data query in onStart() instead of onCreate(). I personally think that onCreate() should be used only for operations you would otherwise perform in the constructor (e.g. fields initializations). Another reason to perform this query in onStart() is that if the user leaves your application for a long time and then gets back to it, the data might get outdated (e.g. due to background syncs by SyncAdapter) and you'll want to refresh it.
The last piece that you might want to add is "data change notifications". You will want this mechanism to be in place if the data that you query and display to the user can change without user's interaction (e.g. due to background syncs by SyncAdapter). The concept is simple - Activity registers for notifications about data change in onCreate(), and if notification received you perform re-query in order to make sure that the user sees an up-to-date data.
I can't claim that the above are "best practices", but they are good practices that work well.
Lazy list:
As #pskink suggested in his comment, you could also employ LazyList. Be aware, though, that it doesn't obviate a need for async query of data. Usage of LazyList allows you to perform the query as usual, but load the results into memory in on-demand way. This might be useful if you expect the query to produce lots of data.
In my opinion, however, one should optimize the code only if actual performance problem is being observed. So, unless you know ahead of time that a particular query produces thousands of results, I say you don't need LazyList.
Hello Everybody,
    I have an app that a couple of classmates and I did for a class project. We are now continuing the app to improve and streamline it. Here is what I have so far:
SplashScreen
HomePage
Hole 1 - 18
ScorePage
AboutPage
Home Screen Hole 1 - 18 Score Page About Page
I have pretty much got it all figured out except for a few small things. The issue that I am working on right now though is:
Passing data from each Hole Page to the Score Page.
I know how to pass it from page to page and I could brute force it, because that is how I initially had it, but it looks sloppy and I would like to not do that if possible.
//Code (partial)
//(From Hole 1)
#Override
public void onClick(View v)
{
TextView tvScoreLbl = (TextView) findViewById(R.id.scoreLbl);
tvScoreLbl.setText(String.valueOf(count));
if(v == findViewById(R.id.btnAdd))
{
count++;
tvScoreLbl.setText(String.valueOf(count));
}
else if(v == findViewById(R.id.btnMinus))
{
count--;
tvScoreLbl.setText(String.valueOf(count));
}
else if(v == findViewById(R.id.btnPrev))
{
Intent i_prev = new Intent(Hole_01.this, HomePage.class);
startActivity(i_prev);
}
else if(v == findViewById(R.id.btnNext))
{
Intent i_pass = new Intent(Hole_01.this, ScorePage.class);
i_pass.putExtra("score1", tvScoreLbl.getText().toString());
Intent i_next = new Intent(Hole_01.this, Hole_02.class);
startActivity(i_next);
}
//(From ScorePage)
String score;
TextView tvScore1 = (TextView) findViewById(R.id.tvScore1);
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.scorepage);
Button btnHome = (Button) findViewById(R.id.btnHome);
btnHome.setOnClickListener(this);
score = getIntent().getExtras().getString("score1");
tvScore1.setText(score);
}
Thanks in advance.
The best answer depends on how long you need your data to persist.
If you need it to persist across a long period of time (days/weeks) then store the data to the internal or external storage using either SQLite or creating a custom object that represents all the data from the 'hole page' and serialize that object and write it to disk.
If you don't need the data to persist then I'd do as you currently are and continue passing the data as you are, from Activity to Activity. It may seem 'sloppy' to you but it is how you are supposed to pass data between Activities.
Perhaps creating an object for the data you want to pass and have it implement the Parcelable interface would make it less 'sloppy' in your eyes. Here's a link to a tutorial for Parcelable objects.
Another option is to use the global Application context as suggested already BUT be warned it comes with a big problem - if the app is killed in the background any data stored in the Application class is lost, so you'll have to account for that happening.
A DB is excessive if you're only storing a tiny bit of info, the Application context will lose data if the app is killed by the OS.
So if you want to persist data for a period of time, use serialization to write the object ( if it's just the score then a simple File IO stuff would do the job with no need for serialization). Or if you only need it to persist while the app is running (foreground or background) then pass the data between Activities.
Rather than passing data between all your Intents with putExtra why not use a little SQLite DB to store the result of each activity that the score screen could read.
SQLite Docs
Android Notepad Tutorial is good for SQLite example as well
Hold all the state in an Application Class.
Write to it as needed, from whatever Activity.
Read from it in the ScorePage Activity.
More details and example available here: (Using the Android Application class to persist data)