Two way binding Android - java

Maybe I am missing something small here, but I cannot get my binding to work. I set it up as follow:
public class Toolbar extends Fragment {
//Interaction handlers
//interface for interaction with Activity
public interface ToolBarInteraction{
public void Search(String screenName);
}
private ToolbarBind modelData;
private ToolBarInteraction mListener;
public static Toolbar newInstance() {
return new Toolbar();
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
FragmentToolbarBinding binding = DataBindingUtil.setContentView(getActivity(), R.layout.fragment_toolbar);
modelData = ToolbarBind.newInstance();
modelData.searchedText.set("Hello");
binding.setModelData(modelData);
}
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
checkMListener();
View view = inflater.inflate(R.layout.fragment_toolbar, container, false);
//get button to set onClick event
Button button = (Button)view.findViewById(R.id.btnSearch);
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
String hello = modelData.searchedText.get();
}
});
return view;
}
public void OnSearchClicked(View view){
mListener.Search(modelData.searchedText.get());
}
private void checkMListener(){
try{
mListener = (ToolBarInteraction) getActivity();
} catch (ClassCastException ex) {
throw new ClassCastException(getActivity().toString()
+ " must implement the ToolBarInteraction Interface");
}
}
}
Here is the code for ToolbarBind:
public class ToolbarBind extends BaseObservable {
private String _searchText;
public final ObservableField<String> searchedText = new ObservableField<String>();
//factory method
public static ToolbarBind newInstance(){ return new ToolbarBind(); }
}
And in my fragment, I set the binding up as follow, all within the layout tag:
<data>
<variable
name="modelData"
type="Common.CommonObjects.ToolbarBind"/>
</data>
And for binding to property:
<EditText
android:layout_width="match_parent"
android:layout_height="match_parent"
android:hint="Search"
android:text="#={modelData.searchedText}"/>
As can be seen, in the onCreate I set the text to "Hello", but even when the view displays on the phone, the EditText is not populated with this text. When I change the value, and click my button, the value I get back in the event is "Hello", not my new text entered while the app is running.
What am I missing?

The problem with your code is that you set the Activity's content view to something in onCreate(...) but you inflate and use something different in onCreateView(...) as your fragment's view, which gets the model data (not the other one you created in onCreate(...)). I don't know exactly what you try to achieve, but I'm gonna guess that you don't want to change the Activity's content view to something from the fragment, so I'm just gonna show you a variation of what could you use, however, you should change it to whatever pleases you.
Remove onCreate(...) completely then use only onCreateView(...) to inflate the fragment_toolbar layout using data binding:
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
checkMListener();
FragmentToolbarBinding binding = DataBindingUtil.inflate(inflater, R.layout.fragment_toolbar, container, false);
// FIXME you're losing data here; watch out: checking whether savedInstanceState == null is not enough because returning from backstack it will be null
modelData = ToolbarBind.newInstance();
// FIXME modify this so it sets the data from savedInstanceState when configuration changes
modelData.searchedText.set("Hello");
binding.setModelData(modelData);
//get button to set onClick event
binding.btnSearch.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
String hello = modelData.searchedText.get();
}
});
return binding.getRoot();
}
Watch out for the FIXME parts. You could move the modelData init to onCreate(...) which would save it from the backstack-return thing, however, configuration change will still call onCreate(...) unless you call setRetainInstance(true) on the fragment (do not).

Related

Android Navigation Component - Why add arguments in the navigation graph?

I've been following CodingWithMitch's tutorial (and github code) to Navigation Component, but used Java instead of Kotlin. That hasn't brought any issues however.
My question is, without using Safe Args, what is the point to adding arguments in the nav_graph.xml.
In this example, SpecifyAmountFragment requires a String argument from the previous Fragment, called ChooseRecipientFragment:
Design View of nav_graph.xml:
Code snippet from the nav_graph.xml:
<fragment
android:id="#+id/specifyAmountFragment"
android:name="com.asfartz.navigation_component_basics.SpecifyAmountFragment"
android:label="fragment_specify_amount"
tools:layout="#layout/fragment_specify_amount">
<argument android:name="recipient"
app:argType="string" />
<action
android:id="#+id/action_specifyAmountFragment_to_confirmationFragment"
app:destination="#id/confirmationFragment"
app:enterAnim="#anim/slide_in_right"
app:exitAnim="#anim/slide_out_left"
app:popEnterAnim="#anim/slide_in_left"
app:popExitAnim="#anim/slide_out_right"
app:popUpTo="#id/mainFragment"
app:popUpToInclusive="false" />
</fragment>
Java code:
public class SpecifyAmountFragment extends Fragment {
private NavController navController;
private Button bSend, bCancel;
private String recipient;
private TextView tvRecipient;
private TextInputEditText inputAmount;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_specify_amount, container, false);
bSend = view.findViewById(R.id.send_btn);
bCancel = view.findViewById(R.id.cancel_btn);
tvRecipient = view.findViewById(R.id.recipient);
inputAmount = view.findViewById(R.id.input_amount);
recipient = getArguments().getString("recipient");
String messagge = "Sending money to " + recipient;
tvRecipient.setText(messagge);
return view;
}
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
navController = Navigation.findNavController(view);
bSend.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (!TextUtils.isEmpty(inputAmount.getText())) {
Money money = new Money(new BigDecimal(inputAmount.getText().toString()));
Bundle b = new Bundle();
b.putString("recipient", recipient);
b.putParcelable("money", money);
navController.navigate(R.id.action_specifyAmountFragment_to_confirmationFragment, b);
} else {
Toast.makeText(getActivity(), "Enter an amount", Toast.LENGTH_SHORT).show();
}
}
});
bCancel.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (getActivity() != null) {
getActivity().onBackPressed();
} else {
Toast.makeText(getContext(), "Activity is null", Toast.LENGTH_SHORT).show();
}
}
});
}
}
I'm receiving the bundle in OnCreateView, from which I'm taking the required String argument that I've declared in the nav_graph.xml. And later on, I'm sending this data forward in another bundle (in the bSend.setOnClickListener(...)).
If I comment out all the <argument> tags and run the app again, the app will still work as it should (receiving the data from one fragment and passing it to another). There is no validation, so why add these tags at all, besides for clarity maybe?
When using Navigation architecture component, you are not suppose to use
recipient = getArguments().getString("recipient");
Instead, you should use generated class of the destination which should be SpecifyAmountFragmentArgs and get your data this way:
SpecifyAmountFragment.fromBundle(Pass arguments here).getRecipient which will provide type safety.

Android fragment navigation without button click

I am using a "Drawer Navigation" project using fragments and so far this method works:
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Navigation.findNavController(view).navigate(R.id.nav_gallery);
}
});
But I want to use the navigation method inside a fragment class calling a function like this:
boolean b;
public void fragmentNavigation() {
if (b) {
Navigation.findNavController(getView()).navigate(R.id.nav_gallery);
}
}
I am a newbie using the navigation architecture and I still don't know if I need to declare some type of action listener for that function or how to make it work.
You can do that, but be careful of :
using getView() is Nullable; so you must make sure that your fragment has already created the view.
You can solve this by a couple of ways
First: Overriding onViewCreated() which has a nonNull view argument
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
fragmentNavigation(view);
}
boolean b;
public void fragmentNavigation(View view) {
if (b) {
Navigation.findNavController(view).navigate(R.id.nav_gallery);
}
}
Second: Create a View field in the fragment class, and set it within onCreateView()
View view;
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container,
#Nullable Bundle savedInstanceState) {
view = inflater.inflate(R.layout.main_fragment, container, false);
return view;
}
then always call fragmentNavigation(view); on that view field.
Your fragment is hosted by the NavHostFragment of the Navigation Graph; so that you can avoid potential IllegalStateException

Android Fragment Back press

I have a couple of fragments. But all the fragments lead to PeriodFragment when they press a button. My question is how do I implement so that when the user presses the BACK button on his mobile phone, the PeriodFragment will switch back to the fragment it was entered from.
This is the java code from PeriodFragment.java:
public class PeriodFragment extends Fragment {
Button btnPretrazi;
public PeriodFragment() {
// Required empty public constructor
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_period, container, false);
//buttonPretraziPeriod
btnPretrazi = (Button) view.findViewById(R.id.buttonPretraziPeriod);
btnPretrazi.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Toast.makeText(getContext(),"test", Toast.LENGTH_SHORT).show();
}
});
return view;
}
}
This is my TvrdjavaFragment.java (one of the fragments that have the button to switch to PeriodFragment.java) :
package com.example.ivanp.msnis;
public class TvrdjavaFragment extends Fragment {
Button btnIdinaperiod;
public TvrdjavaFragment() {
// Required empty public constructor
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_tvrdjava, container, false);
// Inflate the layout for this fragment
//show date
TextView datumprikaz = (TextView) view.findViewById(R.id.datumprikaz);
Date danas = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy");
String novDatum = sdf.format(danas);
datumprikaz.setText(novDatum);
//End of show date
btnIdinaperiod = (Button) view.findViewById(R.id.buttonIdinaperiod);
btnIdinaperiod.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
PeriodFragment periodFragment = new PeriodFragment();
FragmentTransaction periodFragmentTransaction = getActivity().getSupportFragmentManager().beginTransaction();
periodFragmentTransaction.replace(R.id.frame, periodFragment);
periodFragmentTransaction.commit();
}
});
return view;
}
}
I'm new to android studio, so please tell the details.
According to android docs When using fragments in your app, individual FragmentTransaction objects may represent context changes that should be added to the back stack. For example, if you are implementing a master/detail flow on a handset by swapping out fragments, you should ensure that pressing the Back button on a detail screen returns the user to the master screen. To do so, call addToBackStack() before you commit the transaction:
// Works with either the framework FragmentManager or the
// support package FragmentManager (getSupportFragmentManager).
getSupportFragmentManager().beginTransaction()
.add(detailFragment, "detail")
// Add this transaction to the back stack
.addToBackStack()
.commit();
Adding to backstack will solve the problem:
periodFragmentTransaction.replace(R.id.frame, periodFragment);
periodFragmentTransaction.addToBackStack(null);
periodFragmentTransaction.commit();

Android - where to put onClick on linearlayout of a fragment

Obviously, I am new to Android - XML programming... So I have a navigation drawer, and once item is selected from the drawer, a corresponding fragment on the right side will display. Inside that fragment, I have linear layouts. I'd like to get redirected to another activity once that linear layout has been tapped. I was able to make it work on activities, by using android:onClick on XML file, but can't make it work on fragment. Somebody help me please.
App interface, refer to this image:
http://i.stack.imgur.com/u6dHi.jpg
Code:
fragment_smart.xml - the display once item's selected. I am trying to use the onClick on xml.
<LinearLayout
android:id="#+id/linearLayoutsmart1"
android:layout_width="match_parent"
android:layout_height="30dp"
android:layout_below="#+id/smart_title"
android:layout_marginLeft="15dp"
android:layout_marginTop="15dp"
android:orientation="horizontal"
android:weightSum="1"
android:onClick="smart_recommended_link">
Here's my Java code:
public class FragmentSmart extends Fragment {
public static final String TAG = "stats";
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View myfragment = inflater.inflate(R.layout.fragment_smart, container, false);
return myfragment;
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
}
public void smart_recommended_link(View view) {
Intent smartRecommendedIntent = new Intent(this, SmartRecommended.class);
startActivity(smartRecommendedIntent);
}
}
The app is crashing when I clicked on the linearlayout using this code. What's the best thing to do here? Thank you!
For fragments you need to add the listener programmatically:
public class MyFragment extends Fragment implements View.OnClickListener {
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
view.findViewById(R.id.my_layout).setOnClickListener(this);
}
#Override
public void onClick(View v) {
// Handle click based on v.getId()
}
}
First of all, linear layouts cant trigger onClick events by default.
Check this answer for more information: LinearLayout onClick.
You can get the LinearLayout from the view in your onCreateView() like this:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View myfragment = inflater.inflate(R.layout.fragment_smart, container, false);
LinearLayout layout = (LinearLayout)myFragment.findViewById(R.id.linearLayoutsmart1);
// here you can set a listener of any type you want to the layout
return myfragment;
}
In fragments, you must use getActivity() method instead of this to reference the activity that the fragment is attached to.
public void smart_recommended_link(View view) {
Intent smartRecommendedIntent = new Intent(getActivity(), SmartRecommended.class);
startActivity(smartRecommendedIntent);
}

Transferring multiple strings between Fragments of the same activity with tags

I am having trouble figuring out how to share data between my two fragments which are hosted on the same activity.
The objective:
I want to transfer string from the the selected position of a spinner and an image url string from a selected list view position from fragment A to fragment B.
The Attempt:
I read the fragments doc on this problem here http://developer.android.com/guide/components/fragments.html#CommunicatingWithActivity
And went ahead an created the following Interface to use betweeen the Fragments and the Host Activity.
public interface OnSelectionListener {
public void OnSelectionListener(String img, String comments );
}
Then I proceeded to implement it in my fragment A's onCreateView method like so:
postList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
ListData link = data.get(position);
String permalink = link.getComments();
String largeImg = link.getImageUrl();
Fragment newFragment = new DetailsView();
FragmentTransaction transaction = getFragmentManager().beginTransaction();
// Replace whatever is in the fragment_container view with this fragment,
// and add the transaction to the back stack
transaction.replace(R.id.fragment_container, newFragment);
transaction.addToBackStack(null);
// Commit the transaction
transaction.commit();
//pass data to host activity
selectionListener.OnSelectionListener(permalink,largeImg);
}
});
And also in the onAttach method
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
selectionListener = (OnSelectionListener)getActivity();
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString() + " must implement onSelectionListener");
}
}
In the Host activity I implemented the interface I wrote and overrided the method like so:
#Override
public void OnSelectionListener(String img, String comments) {
DetailsView detailsView = new DetailsView();
DetailsView dView = (DetailsView)getSupportFragmentManager().findFragmentByTag(detailsView.getCustomTag());
dView.setInformation(img, comments);
}
In Fragment B I set a "tag" the following way
private String tag;
public void setCustomTag(String tag)
{
this.tag = tag;
}
public String getCustomTag()
{
return tag;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setCustomTag("DETAILS_VIEW");
And my thinking is that that the information can be passed to Fragment B by calling this method from the host activity
void setInformation (String info, String img){
RedditDetailsTask detailsTask = new RedditDetailsTask(null,DetailsView.this);
detailsTask.execute(info);
setDrawable(img);
}
What I need:
I want to know how to properly use tags to get this to work, I dont have any fragment id's declared in my xml and rather opted to exchange fragments in a fragment_container.
I also am not sure if this is a good way to pass multiple strings between fragments. I am a newbie programmer so I know my logic probably looks pretty embarrassing but I am trying to do my best learn to do this right. I would appreciate it if you more senior developers can point me in the right direction for doing this.
You don't need to use tags. Take a look at this example. The Activity implements an interface that allows you to talk from Fragment1 back to the Activity, the Activity then relays the information into Fragment2.
I've left out all the android stuff about FragmentManager etc.
interface FragmentListener {
void onTalk(String s1);
}
public class MyActivity extends Activity implements FragmentListener {
Fragment2 fragment2;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my);
// Find fragment2 and init
}
#Override
public void onTalk(String s1) {
fragment2.onListen(s1);
}
private static class Fragment1 extends Fragment {
private FragmentListener communication;
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
communication = (FragmentListener) activity;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_one, container, false);
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// or in an onClick listener
communication.onTalk("blah blah");
}
}
private static class Fragment2 extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_two, container, false);
}
public void onListen(String s1) {
Log.d("TADA", s1);
}
}
}
My approach would be, when you get the callback in activity through the OnSelectionListener interface, I would create the Fragment B object and set arguments to it as follows:
#Override
public void OnSelectionListener(String img, String comments) {
DetailsView detailsView = new DetailsView();
Bundle args=new Bundle();
args.putString("img",img);
args.putString("comments",comments);
detailsView.setArguments(args);
//code here to replace the fragment A with fragment B
}
Then in Fragment B's onCreate method you can retrieve the values as follows:
#Override
public void onCreate(Bundle savedInstanceState) {
Bundle args=getArguments();
String img=args.getString("img");
String comments=args.getString("comments");
//do whatever you want to do with the varaibles
}
You could try to make two public static String's in your B fragment.
it Would look like something like this
public static String img;
public static String comment;
The you set the variables before making the transaction to fragment B
FragmentTransaction transaction = getFragmentManager().beginTransaction();
// Replace whatever is in the fragment_container view with this fragment,
// and add the transaction to the back stack
transaction.replace(R.id.fragment_container, newFragment);
transaction.addToBackStack(null);
SecondFragment.img = new String("imgString"); //Making a new string so incase you change the string in bfragment, the values wont change in here
SecondFragment.comment = new String("comment");
// Commit the transaction
transaction.commit();
Then in the onStop(), or onDestroy() - depending on when you want the variables to be null, check this - you set the the static variables to null, so they dont take memory space
public void onDestroy(){
super.onDestroy();
img = null;
comment = null;
}

Categories