Is there better way to navigate in Android based on source fragment? - android

I have fragments S1, S2, P, D1, and D2.
Both S1 and S2 have action leading to P. When I am in fragment P I can navigate to D1 or D2.
When I came from S1 I want to navigate to D1, but when I came from S2 I want to go to D2.
What is the best way to do navigate based on the source fragment?.
I know I can use arguments for this, but it seems like a much more basic operation. I would expect some more clear and quicker solution (for example some method getNameOfSource()).

You can do this via getting the entry from your Fragment Backstack, although you need to ensure you are providing some sort of TAG to them in the first place. You can simply use something like this then. Using arguments is still better I would say, but that is out of scope for this question
private fun getNameOfSource(): String? {
val fm = supportFragmentManager
val count = fm.backStackEntryCount
return fm.getBackStackEntryAt(count - 2).name
}

You can get the fragment tag using this code:
public Fragment getSourceFragmentName() {
// you need to check this because you need at least 2 fragments at the backstack
if (getSupportFragmentManager().getBackStackEntryCount() < 2) {
return null;
}
String fragmentTag = getSupportFragmentManager().getBackStackEntryAt(getSupportFragmentManager().getBackStackEntryCount() - 2).getName();
return getSupportFragmentManager().findFragmentByTag(fragmentTag);
}

Related

Fragment retrieved from FragmentManager sometimes has its RecyclerView Adapter set to null

In my application I have one Fragment which is responsible for displaying a list of news items. It takes a String parameter which determines which url to pull data from.
I set the Fragment with this code:
private void setFragment(String pageToLoad, NewsFeedFragment newsFeedFragment) {
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
if(newsFeedFragment == null) {
transaction.replace(R.id.container, NewsFeedFragment.newInstance(pageToLoad), pageToLoad);
}
else {
transaction.replace(R.id.container, newsFeedFragment, pageToLoad);
}
mPageToLoad = pageToLoad;
}
In my parent Activity I keep track of which 'page' is currently being viewed:
protected void onSaveInstanceState(#NonNull Bundle outState) {
if(mPageToLoad != null) {
outState.putString("pageToLoad", mPageToLoad);
}
super.onSaveInstanceState(outState);
}
In my parent Activity onCreate method I check whether an instance of NewsFeedFragment has been created and added to the FragmentManager as follows:
protected void onCreate(Bundle savedInstanceState) {
if (savedInstanceState != null) {
if (savedInstanceState.containsKey("pageToLoad")) {
String pageToLoad = savedInstanceState.getString("pageToLoad");
if(pageToLoad != null) {
NewsFeedFragment newsFeedFragment = (NewsFeedFragment) getSupportFragmentManager().findFragmentByTag(pageToLoad);
if(newsFeedFragment != null) {
setFragment(pageToLoad, newsFeedFragment);
}
else {
setFragment(pageToLoad, null);
}
}
}
}
}
This works well 99% of the time, the application resumes correctly and displays the last instance of NewsFeedFragment added. However, I have an issue which seems to occur randomly where the RecyclerView Adapter in NewsFeedFragment is sometimes null when the Fragment is retrieved from the FragmentManager using the findFragmentByTag(pageToLoad) method.
In NewsFeedFragment the RecyclerView Adapter is a class variable:
public NewsPageAdapter mNewsPageAdapter;
The onActivityCreated method of NewsFeedFragment is as follows:
public void onActivityCreated(Bundle savedInstanceState) {
if(mNewsPageAdapter == null) {
Log.i(TAG, "mNewsPageAdapter is null"); // This is logged when issue occurs
}
if(savedInstanceState == null || mNewsPageAdapter == null) {
new LoadFirstPageTask().execute(); // Fetches news items from web service, creates mNewsPageAdapter, and then calls setupRecyclerView() method
}
else {
setupRecyclerView(savedInstanceState);
}
}
Finally, this is the NewsFeedFragment setupRecyclerView method:
private void setupRecyclerView(Bundle savedInstanceState) {
mRecyclerView.setLayoutManager(mLayoutManager);
mRecyclerView.setAdapter(mNewsPageAdapter);
}
From what I've described could anyone offer any insight as to why the NewsPageAdapter may sometimes be null when the Fragment is retrieved from The FragmentManager?
Thanks
Ok, so you do new LoadFirstPageTask().execute(); and I guess that will eventually call setupRecyclerView?
I think you're over complicating a solution here.
You have all these conditions, business logic, and decisions made inside a volatile component (a Fragment) whose lifecycle is quite complex and not always what you'd expect; you also couple the maintenance of asynchronous data to this structure, and this is having unexpected side-effects that are hard to pin point and track down.
Creating the Adapter is "cheap" compared to fetching, processing, and producing the data for said adapter.
You don't seem to mention ViewModels anywhere, are you using a viewModel? Or any other sort of pattern like a Presenter, Interactors, useCases?
AsyncTasks are also Deprecated and while I don't advocate to run to the "refactor" hill every time a class is deprecated, I think you could get a better and more stable, testable, and readable solution if you abstract that AsyncTask into a coroutine (all managed by your ViewModel for example).
To put in other terms, your Fragment and Activity shouldn't have to deal with the logic regarding "do I need to load this data or not"; this is someone else's responsibility.
About your code.
Ok, now that I've ranted about how you're doing things, let's dig deeper into your existing Java code.
the RecyclerView Adapter in NewsFeedFragment is sometimes null when the Fragment is retrieved from the FragmentManager using the findFragmentByTag(pageToLoad) method.
Whenever we see "sometimes" in a crash, the 1st suspect should be timing/threading. Synchronous code can fail, but it's often orders of magnitude more predictable than Asynchronous code.
If it's "sometimes" null, then the task that is in charge of changing this behavior is not always ready by the time it's needed; or the condition needed for this task to run, is not always what you expect by the time it's checked, and so on, and so forth.
Start by re-architecting your idea into a separate component.
Have the Fragment create its adapter (can be empty of data) as soon as possible, regardless of whether there's data or not.
Have the fragment ask another component for the data. And when the data is available, send it to the Fragment who will in turn set it in the adapter. If the data is already there by the time you ask for it (because you "cached" it), you won't have to wait.
I'd also store the "last viewed page" in the same component, so you don't need to save the state and pass it alongside to a fragment. Rather the fragment asks for "the current data" and the component already knows what it is.
All in all, it's a bit difficult to put all your pieces together because we, the readers, don't have all the code, nor your requirements that lead you to this solution.

Android fragment safe args

Can somebody explain to me how it's works?
From (MainFragment) tap on FAB to create new fragment(HabitEditorScreenFragment) through navigation component. Applying to it Parcelable argument. In that fragment i'm going in new fragment(EditColorFragment) with new argument type of Int. In that fragment:
saving new int in sharable viewModel
i'm going back with findNavController().navigateUp()
and now is strange things. If i enter something in editfields that values will be in safeArgs value. But i haven't save it in there anywhere!
Update:
It happend riht in time when i type somthing in edit fields.
But:
private fun setDoAfterTextChanged() = with(binding) {
fhesHabitNameInput.doAfterTextChanged {
viewModel.editorHabit.name = fhesHabitNameInput.text.toString()
viewModel.canWeSave() //just cheking errors
checkName() // checking specific field
}
fhesHabitDescriptionInput.doAfterTextChanged {
viewModel.editorHabit.description = fhesHabitDescriptionInput.text.toString()
viewModel.canWeSave()
checkDescription()
}
fhesHabitFrequencyInput.doAfterTextChanged {
viewModel.editorHabit.frequency = fhesHabitFrequencyInput.text.toString()
viewModel.canWeSave()
checkFrequency()
}
}
Here's the code
Ok, i'm figure it out what's happening. Dumb me. :D
viewModel.editorHabit = args.habitCharacteristics
this is changing link on viewModel.EditorHabit not copying info from args. Really stupid :D Sorry for that.

i can't make a contact between the activities in Kotlin

Hello this is my first app with kotlin i am trying to make annual rate calculation app the problem is i have 4 activities every activity own button and edit's texts
i wan't when The User click the button, the program get the numbers from Edit's texts and only make the calculation and save it somewhere and same work for the activity 2 and 3.
but when he click the last button of the last activity i want to call all the results and show it in ViewText
The Question is:How to save data Every time somewhere and call when i need it?
First Activity
class st {
var int_P: Double? = null
var ctl_P: Double? = null
public constructor(int_P: Any, ctl_P: Any) {
this.int_P = int_P.toString().toDouble() //Physique
this.ctl_P = ctl_P.toString().toDouble()
public fun GetMP(): Double {
return (this.int_P!! + (this.ctl_P!! * 2)) / 3
}
}
Btn_Next1.setOnClickListener ({
var int_P = java.lang.Double.parseDouble(edit_IP.text.toString()) //Physique
var ctl_P = java.lang.Double.parseDouble(edit_CP.text.toString())
var ss = st(int_P,ctl_P)
val ic = Intent(this, Sec_Act::class.java)
startActivity(ic)
})
(Secend and Third Activity Same)
Activity 4
btn1.setOnClickListener ({
var act1 = MainActivity.st().GetMC()
Textv.text = act1.toString()
})
With this method i got problem (no value passed for parameter int_P , ctl_P)
There are many different ways to send information back to an Activity:
onActivityResult(),
having a singleton class,
use Shared Preferences,
headless fragments,
sqlite database,
store the information in a file.
Intents
receivers
You need to determine which will be the best solution for you. Whether it's kotlin or java, the methodology will be the same.

Updating ListView in a Fragment from OnOptionsItemSelected

I have a HomeActivity which extends Activity that contains Actionbar items. The HomeActivity has 1 fragment (StatusFragment which extends Fragment). In the Fragment there is a ListView which uses a custom ArrayAdapter and a method call to supply the data.
private ParseUser[] GetUsers(){
final ParseQuery<ParseUser> query = ParseUser.getQuery();
ParseUser[] usersArray;
try {
List<ParseUser> users = query.find();
usersArray = users.toArray(new ParseUser[users.size()]);
} catch (ParseException e) {
usersArray = null;
e.printStackTrace();
}
return usersArray;
}
I'm having trouble getting the ListView to update from the OnOptionsItemSelected callback.
case R.id.home_ab_refresh:
StatusFragment pFrag = (StatusFragment) getFragmentManager().findFragmentByTag("mFragment");
pFrag.users = pFrag.GetUsers();
pFrag.mAdapter.notifyDataSetChanged();
return true;
1) Is this an appropriate way to access the Fragment from the Actionbar items (HomeActivity)?
2) Is there a better way to design this code?
Thanks much!
Re 1) I probably wouldn't do a findFragmentByTag() every time, and instead just stick the fragment into a member variable of the activity during the activity's onCreate().
The main issue with the code is something else:
pFrag.users = pFrag.GetUsers();
pFrag.mAdapter.notifyDataSetChanged();
Here you violate the object-oriented design principle of loose coupling. The HomeActivity is too intimately bound to the implementation details of the StatusFragment. What you should do instead is move that code into the fragment and expose it as a single, public method that is named for the intent (goal, purpose) of the action, not its implementation.
// In HomeActivity
pFrag.reloadData();
// In the fragment
public void reloadData() {
this.users = pFrag.GetUsers();
this.mAdapter.notifyDataSetChanged();
}
This way, it's easier to reuse the status fragment elsewhere. More importantly, it's easier to evolve that fragment, you can now completely change the internals without having to change the host activity. This is cleaner from a design perspective.
Re 2) Aside from the issue I already mentioned, you should consider returning an empty array rather than null when an exception occurs. It's generally a better idea to return empty array/collection from finder methods, because people tend to immediately use the result for an iterator or an addAll() or something like that, without null-checking it first.
First of all, you dont make a nullpointer check, since you cant be certain that the FragmentManager will actually return a validFragment.
you can however just catch the onOptionsMenuSelected event in the fragment itself, which will result in a much more capsulated code.
Besides that, when do you refresh the ListView? Wouldnt it make sense to update the listview automatically once the new data has arrived?

Basics ---> Checking strings using if else to set value of an int, possible wrong use of onResume

So I'm still working on my first little app here, new to Android and Java, so I'm stuck on a basic little problem here. Answers to my first questions were really helpful, so after researching and not coming up with anything, I thought I'd ask for some more help!
The idea is that on another screen the user makes a choice A, B, C, or D, and that choices is passed as a string through the intent. OnResume checks if the choice is not null and sets an integer that corresponds to that string. Later when the user pushes another button, some if else logic checks that int and performs and action based on which was chosen. The problem is that the App crashed at onResume.
I learned that I have to use equals(string) to compare string reference, but maybe the problem is that I am trying to compare a string in reference to a literal string? Any help would be greatly appreciated.
Thanks!
protected void onResume() {
super.onResume();
// Get the message from the intent
Intent intent = getIntent();
String choice = intent
.getStringExtra(ExtensionSetupSlector.TORQUE_SETUP);
// Create the text view
TextView displayChoice = (TextView) findViewById(R.id.displayChoice);
if (!choice.equals("")){
displayChoice.setText(choice);
if (choice.equals("A")) {
myChoice = 1;
}
if (choice.equals("B")) {
myChoice = 2;
}
if (choice.equals("C")) {
myChoice = 3;
}
if (choice.equals("D")) {
myChoice = 4;
}
}
}
myChoice is declare right after ...extends Activity{ Also I'm not quite sure If this should really be in onResume, but it was working before I started try to set myChoice in the onResume (when I was just displaying the choice). Thanks again!
Change if (!choice.equals("")) to check for null instead. Otherwise your app attempts to access an empty reference and crashes.

Categories

Resources