Using Dagger with Nested Fragment and Views - java

I'm still trying to grasp the Dagger mindset of Dependency Injection, and am running into some trouble. I have a MyNavBar which is a View in a fragment. I can't figure out how to inject my app's MainWrapper class into it. Here's how my code is laid out:
I have a MainActivity in my app's Android module:
public class MainActivity extends Activity {
private MainWrapper mainWrapper;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//Create the basic interface which will have parts swapped out with
// fragments in MainWrapper:
this.setContentView(R.layout.main);
ObjectGraph graph = ObjectGraph.create(new MyDaggerModule(this));
this.mainWrapper = new MainWrapper(graph);
}
Then, MainWrapper exists in the library which contains almost all of my app's code:
public class MainWrapper {
#Inject
protected MenuFragment menu;
#Inject
protected Activity activity;
private ObjectGraph objGraph;
public MainWrapper(ObjectGraph objGraph) {
this.objGraph = objGraph;
this.objGraph.inject(this); //populated this.menu and this.activity
Fragment mainFragment = new MainFragment();
this.objGraph.inject(mainFragment);
//Transaction to swap out blank fragment for mainFragment
// ...
}
}
MainFragment is a pretty standard fragment:
public class MainFragment extends Fragment {
protected MyNavBar navBar; //LinearLayout
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View mainContentView = inflater.inflate(R.layout.main_content, container, false);
this.navBar = (MyNavBar) mainContentView.findViewById(R.id.main_nav_bar);
return mainContentView;
}
}
Lastly, here's the MyNavBar fragment:
public class MyNavBar extends UILinearLayout {
#Inject
protected MainWrapper wrapper;
protected View menuButton;
//Constructors that call init() at the end
// ...
protected void init() {
this.setContentView(R.layout.navbar);
this.menuButton = this.findViewById(R.id.menuBtn);
this.menuButton.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
//I want to call methods on MainWrapper here.
}
});
}
}
How can I get MainWrapper to inject wrapper into that MyNavBar class using DI? I realize I could inject MainWrapper into MainFragment, then create a setter on MyNavBar, but that seems to defeat the purpose of DI in the first place.
I guess ultimately what I'm asking is this: If you have your main Activity, which creates a fragment, and that fragment has views, how can you inject into those fragment's views while keeping the ObjectGraph only in the main activity? At some point, won't you have to call objectGraph.inject() on the fragment's Views?

Related

Base Activity extending Activity ViewBinding Crash

I have Base Activity extends My all other activities. The Base Activity has features like toolbar, progress bar, and other logic methods that are similar across the activity
After Migrating from ButterKnife to ViewBinding The child activity that is extended from the parent base is not able to access the methods in it And the app gets crashed.
Below is Base Activity Code
pubic class BaseActivity extends AppCompatActivity {
private ActivityBaseBinding activityBaseBinding;
#Override
public void setContentView(int layoutResID) {
activityBaseBinding = ActivityBaseBinding.inflate(getLayoutInflater());
View view = getLayoutInflater().inflate(layoutResID, activityBaseBinding.container, false);
if (layoutResID == R.layout.activity_home) {
activityBaseBinding.toolbarTitle.setVisibility(View.GONE);
} else if (layoutResID == R.layout.activity_my_account) {
activityBaseBinding.toolbarTitle.setVisibility(View.VISIBLE);
} else {
activityBaseBinding.toolbarTitle.setVisibility(View.GONE);
}
activityBaseBinding.container.addView(view);
setContentView(activityBaseBinding.getRoot());
activityBaseBinding.imgBackArrow.setOnClickListener(v -> onBackPressed());
}
If I try to access the toolbar in the base from the child activity this is the error I get
java.lang.RuntimeException: Unable to start activity
ComponentInfo{com.iowave.scheduler/com.example.myaccount.views.MyAccount}:
java.lang.NullPointerException: Attempt to read from field
'com.google.android.material.appbar.MaterialToolbar
com.example.databinding.ActivityBaseBinding.toolbarTitle' on
a null object reference
This is the code of my child activity
public class MyAccount extends BaseActivity implements MyAccountImpl {
private NavController navController;
private ActivityMyAccountBinding activityMyAccountBinding;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
activityMyAccountBinding = ActivityMyAccountBinding.inflate(getLayoutInflater());
View view = activityMyAccountBinding.getRoot();
setContentView(view);
}
}
From what I understand the view binding is only binding the child view and removing all other bindings from the background.
Use this approach for base activity patterns.
abstract class BaseActivity1<Binding extends ViewBinding> extends AppCompatActivity {
Binding binding;
abstract Binding getBinding();
void initBinding(){
binding=getBinding();
}
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.initBinding();
setContentView(binding.getRoot());
}
}
then
class MainActivity extends BaseActivity1<ActivityMainBinding>{
#Override
ActivityMainBinding getBinding(){
return ActivityMainBinding.inflate(getLayoutInflater());
}
}
This could be solve your problem:
I just migrated the project from Butterknife to ViewBinding and face problems related implementing encapsulated activities view into base activity' container, too. Before implementing ViewBinding, I was using getLayoutInflater().inflate(layoutResID, activityContainer, true); where layoutResID was id of encapsulated activities' xml layout (R.layout.activity_main) in BaseActivity so I could inflate encapsulated activities' xml inside activityContainer which is corresponding to FrameLayout inside BaseActivity's xml. But then I figure out that, with ViewBinding, there is no ViewGroup id (xml) but View. So I couldn't use getLayoutInflater().inflate(layoutResID, activityContainer, true); because layoutResID is a ViewGroup id, not View. Here is how I solved.:
abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
private var _binding: VB? = null
val childActivityBinding: VB
get() = _binding as VB
abstract fun inflateLayout(
parent: FrameLayout,
inflater: LayoutInflater
): VB
lateinit var baseActivityBinding: ActivityBaseBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
baseActivityBinding = ActivityBaseBinding.inflate(layoutInflater)
_binding = inflateLayout(baseActivityBinding.root.layout_container, layoutInflater)
val coordinatorLayout = baseActivityBinding.root
.
.// some base activity processes like setting Snackbar etc.
val snack = coordinatorLayout.snack
.
super.setContentView(coordinatorLayout)
}
}
Here is Main Activity: [ in Java :) ]
public class MainActivity extends BaseActivity<ActivityMainBinding> {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#NonNull
#Override
public ActivityMainBinding inflateLayout(#NonNull FrameLayout parent, #NonNull LayoutInflater inflater) {
return ActivityMainBinding.inflate(inflater, parent, true);
}
}
With this approach, I was able to inflate the encapsulated activities view inside base activity like a subview.

How to set public textview in activity from fragment?

I want to set the text of textview in activity from a fragment. This is how I do it.
MainActivity.java
public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener {
public TextView textViewNotification;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
NavigationView navigationView = findViewById(R.id.nav_view);
navigationView.setNavigationItemSelectedListener(this);
textViewNotification = (TextView) MenuItemCompat.getActionView(navigationView.getMenu().findItem(R.id.nav_notification));
}
}
HomeFragment.java
public class HomeFragment extends Fragment {
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
((MainActivity)getActivity()).getSupportActionBar().setTitle("PIT IAI & FIP Regional");
ViewGroup root = (ViewGroup) inflater.inflate(R.layout.fragment_home, container, false);
MainActivity activity = (MainActivity) getActivity();
activity.notification.setText("This is a test"); // => got error here.
return root;
}
}
But it didin't work. This is the error that I got:
android.content.res.Resources$NotFoundException: String resource ID #0x1
at android.content.res.Resources.getText(Resources.java:348)
at android.widget.TextView.setText(TextView.java:5846)
How is it exactly to get and set public attributes of activity from fragments? is it not possible? Please help.
Supposed you want to send the text to the activity based on some action.
You can use an interface, first create a public interface in your fragment and add one method inside it which takes one string parameter
public interface CommunicateWithActivity{
void onCommunicate(String s)
}
, declare a global variable mListener of type CommunicateWithActivity,
private CommunicateWithActivity mListener;
then override onAttach and inside try/catch block
#Override
public void onAttach(#NonNull Context context) {
super.onAttach(context);
try {
mListener = (CommunicateWithActivity) context;
} catch (ClassCastException e) {
throw new ClassCastException(context.toString() + "CommunicateWithActivity implementation in Activity is required");
}
}
then in the activity implement the interface an override "onComunicate(String s)" method in the activity and you will get your string inside the methd.
#Override
public void onCommunicate(String s) {
//do whatever you want
}
Henry Gunawan your MainActivity does not have any public field called notification you actually named it textViewNotification.
Moreover i am seeing some bad practices in your code
Avoid declaring public fields instead use getters and setters to access them
Your fragment assuming that it's host is always a MainActivity instance and it has a field called notification , which is not a good practice , fragments are meant to be a standalone unit, fragments should not depends on specifics of their host activity as they may be host by any activity hence this is a misuse of fragments, use should instead use callbacks if you want your host activity to do something for you as explained by Amr Sakr.

Java.lang.ClassCastException: Activity cannot be cast to interface

I am trying to invoke a method named "change_color()" in my one fragment "A" from another fragment "B" using an interface, implemented by the parent activity. When I try to cast my parent activity to the instance of my interface, I get this ClassCastException.
Here's the snippet of fragment "B",
Public class B extends Fragment implements View.onClickListener{
public attendance_to_history var;
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_attendance_take,
container, false);
return rootView;
}
public void onActivityCreated(Bundle savedInstanceState){
super.onActivityCreated(savedInstanceState);
var = (attendance_to_history) getActivity();
}
Here's the code of interface
interface attendance_to_history{
public void invoke();}
Here's code from my parent activity:
public class tabbed_activity extends AppCompatActivity implements attendance_to_history{
#Override
public void invoke() {
fragment_A frag = new fragment_A();
frag.change_color();
}
}
attendance_to_history connector;
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
connector=(attendance_to_history) activity;
}
replace instead for onActivityCreated
here is the solution:
1- in the activity that holds the fragment B, make it implements attendance_to_history and override the method invoke inside the activity.
then your code will work fine.

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

Access getActivity() inside static method

I have this class here which calls the method setPoint
public class PointsList extends Fragment {
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.listpoints, container, false);
public static class PointCreation extends Fragment {
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.point_creation, container, false);
setPoint(view, CREATE);
return view;
}
}
static final void setPoint(View view, int goal) {
final EditText SerialField = (EditText) view.findViewById(R.id.Serial);
if(goal == CREATE) {
Button buttonGuardar = (Button) view.findViewById(R.id.buttonGuardar);
buttonGuardar.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
String Serial = SerialField.getText().toString();
pointsList.add(new Serial);
//go back to R.layout.listpoints
}
});
}
}
My goal is after I click the button to add the new Serial to the List, I can go back to the previous menu from
R.layout.point_creation to R.layout.listpoints
To move around fragments I generally use something like this:
Fragment fragment = new PointsList();
FragmentManager fragmentManager = getActivity().getSupportFragmentManager();
fragmentManager.beginTransaction()
.replace(R.id.container, fragment)
.commit();
But inside:
static final void setPoint(View view, int goal)
getActivity().getSupportFragmentManager();
cannot be referenced from a static context, and I don't know how to go around it with making the static class non-static? I've some global flags which I use in the static Classes (have 2 of them) that would be a bit painfull to export since
public class PointCreation(int something) extends Fragment
is something I can't do.
You can get the activity from view:
Activity activity = (Activity)view.getContext()
If you use FragmentActivity (it seems to be so), then cast Context to FragmentActivity (instead of regular Activity) and further you will able to call getSupportFragmentManager()
FragmentActivity activity = (FragmentActivity)view.getContext();
FragmentManager manager = activity.getSupportFragmentManager();
You can use by below code;
private static FragmentActivity myContext;
#Override
public void onAttach(Activity activity) {
myContext = (FragmentActivity) activity;
super.onAttach(activity);
}
You can use myContext as Context
You can't reference from a static to non-static objects. First thing, that comes to mind is to use singleton pattern for your fragment. In other words add to you fragment singleton snippet:
static PointsList instance;
public PointsList getInstace(){
if(instance == null){
instance = new PointsList ();
}
return instance;
}
and in your fragment onCreate method assign it to the instance:
instance = this;
after that you can remove static modifier from setPoint method. And call it from any part of your project like PointsList.getInstance().setPoint();
p.s. what goals from static you have to use? You should use static very carefully, many things can be done through singleton instead of using statics.

Categories