I have some working knowledge of Android but I'm new to Fragments, Injection, Binding, ButterKnife .etc. I have gone through some videos and tutorials but can't catch where the problem is.
What I want to do:
There is this activity and Code (MeshInteractionActivity.java) which I've got from a GitHub repository. I want to make another activity which is MainActivity.java and connect to this Mesh Activity on button press. I thought it would be just straightforward to do an Intent from MainActivity to MeshInteractionActivity. But it didn't work. I felt maybe it needs a fragment on MainActivity which Intents to 2nd Activity and so I created MainFragment.java. I get the 2 buttons on my MainFragment but when I press Mesh button i.e. mesh()
method in MainFragment I get an app crash.
I tried learning ButterKnife and seeing some videos but they were too basic and I didn't understand how they will fit into my use case. Since my code has a section like this:
@Inject DispatchingAndroidInjector<Fragment> mDispatchingAndroidInjector; @Inject ViewModelProvider.Factory mViewModelFactory;
Things which I've still not got the meaning of.
The error I get:
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.mrinq.mesh_ha_v01/com.mrinq.mesh_ha_v01.MeshInteractionActivity}: java.lang.IllegalArgumentException: No injector factory bound for Class<com.mrinq.mesh_ha_v01.MeshInteractionActivity> at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2426) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2490) at android.app.ActivityThread.-wrap11(ActivityThread.java) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1354) at android.os.Handler.dispatchMessage(Handler.java:102) at android.os.Looper.loop(Looper.java:148) at android.app.ActivityThread.main(ActivityThread.java:5443) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:728) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:618) Caused by: java.lang.IllegalArgumentException: No injector factory bound for Class<com.mrinq.mesh_ha_v01.MeshInteractionActivity> at dagger.android.DispatchingAndroidInjector.inject(DispatchingAndroidInjector.java:106) at dagger.android.AndroidInjection.inject(AndroidInjection.java:61) at com.mrinq.mesh_ha_v01.di.AppInjector.handleActivity(AppInjector.java:88) at com.mrinq.mesh_ha_v01.di.AppInjector.access$000(AppInjector.java:39) at com.mrinq.mesh_ha_v01.di.AppInjector$1.onActivityCreated(AppInjector.java:51) at android.app.Application.dispatchActivityCreated(Application.java:195) at android.app.Activity.onCreate(Activity.java:926) at android.support.v4.app.SupportActivity.onCreate(SupportActivity.java:66) at android.support.v4.app.FragmentActivity.onCreate(FragmentActivity.java:321) at android.support.v7.app.AppCompatActivity.onCreate(AppCompatActivity.java:84) at com.mrinq.mesh_ha_v01.MeshInteractionActivity.onCreate(MeshInteractionActivity.java:87) at android.app.Activity.performCreate(Activity.java:6259) at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1130) at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2379) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2490) at android.app.ActivityThread.-wrap11(ActivityThread.java) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1354) at android.os.Handler.dispatchMessage(Handler.java:102) at android.os.Looper.loop(Looper.java:148) at android.app.ActivityThread.main(ActivityThread.java:5443) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:728) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:618)
My source Activity is MainActivity.java:
public class MainActivity extends AppCompatActivity implements Injectable, HasSupportFragmentInjector, BottomNavigationView.OnNavigationItemSelectedListener, BottomNavigationView.OnNavigationItemReselectedListener, ScannerFragment.ScannerFragmentListener, FragmentManager.OnBackStackChangedListener, NetworkFragment.NetworkFragmentListener, MainFragment.MainFragmentListener, DialogFragmentResetNetwork.DialogFragmentResetNetworkListener { private static final String TAG = MainActivity.class.getSimpleName(); private static final String CURRENT_FRAGMENT = "CURRENT_FRAGMENT"; @Inject DispatchingAndroidInjector<Fragment> mDispatchingAndroidInjector; @Inject ViewModelProvider.Factory mViewModelFactory; @BindView(R.id.state_scanning) View mScanningView; private SharedViewModel mViewModel; private BottomNavigationView mBottomNavigationView; private NetworkFragment mNetworkFragment; private ScannerFragment mScannerFragment; private MainFragment mMainFragment; private Fragment mSettingsFragment; @Override protected void onCreate(@Nullable final Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ButterKnife.bind(this); final Toolbar toolbar = findViewById(R.id.toolbar); setSupportActionBar(toolbar); getSupportActionBar().setTitle(R.string.app_name); mViewModel = ViewModelProviders.of(this, mViewModelFactory).get(SharedViewModel.class); mNetworkFragment = (NetworkFragment) getSupportFragmentManager().findFragmentById(R.id.fragment_network); mScannerFragment = (ScannerFragment) getSupportFragmentManager().findFragmentById(R.id.fragment_scanner); mSettingsFragment = getSupportFragmentManager().findFragmentById(R.id.fragment_settings); mMainFragment = (MainFragment) getSupportFragmentManager().findFragmentById(R.id.fragment_main); mBottomNavigationView = findViewById(R.id.bottom_navigation_view); mBottomNavigationView.setOnNavigationItemSelectedListener(this); mBottomNavigationView.setOnNavigationItemReselectedListener(this); mViewModel.getProvisionedNodesLiveData().observe(this, provisionedNodesLiveData -> { invalidateOptionsMenu(); }); mViewModel.isConnected().observe(this, isConnected -> { if(isConnected != null) { invalidateOptionsMenu(); } }); if(savedInstanceState == null) { onNavigationItemSelected(mBottomNavigationView.getMenu().findItem(R.id.action_network)); } else { mBottomNavigationView.setSelectedItemId(savedInstanceState.getInt(CURRENT_FRAGMENT)); } } @Override public boolean onCreateOptionsMenu(final Menu menu) { if(!mViewModel.getProvisionedNodesLiveData().getProvisionedNodes().isEmpty()){ if(mNetworkFragment.isVisible()) { if (!mViewModel.isConenctedToMesh()) { getMenuInflater().inflate(R.menu.connect, menu); } else { getMenuInflater().inflate(R.menu.disconnect, menu); } } else if(mSettingsFragment.isVisible()){ getMenuInflater().inflate(R.menu.reset_network, menu); } else { return false; } } else { return false; } return true; } @Override public boolean onOptionsItemSelected(final MenuItem item) { final int id = item.getItemId(); switch (id) { case R.id.action_connect: final Intent scannerActivity = new Intent(this, ProvisionedNodesScannerActivity.class); scannerActivity.putExtra(ProvisionedNodesScannerActivity.NETWORK_ID, mViewModel.getNetworkId()); startActivity(scannerActivity); return true; case R.id.action_disconnect: mViewModel.disconnect(); return true; case R.id.action_reset_network: final DialogFragmentResetNetwork dialogFragmentResetNetwork = DialogFragmentResetNetwork. newInstance(getString(R.string.title_reset_network), getString(R.string.message_reset_network)); dialogFragmentResetNetwork.show(getSupportFragmentManager(), null); return true; } return false; } @Override public void onActivityResult(final int requestCode, final int resultCode, final Intent data) { super.onActivityResult(requestCode, resultCode, data); if(requestCode == Utils.PROVISIONING_SUCCESS){ if(resultCode == RESULT_OK){ final boolean result = data.getBooleanExtra("result", false); if(result){ mBottomNavigationView.setSelectedItemId(R.id.action_network); } } } } @Override public boolean onNavigationItemSelected(@NonNull MenuItem item) { final int id = item.getItemId(); final FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); switch (id) { case R.id.action_network: ft.hide(mNetworkFragment).hide(mScannerFragment).hide(mSettingsFragment).show(mMainFragment); break; case R.id.action_scanner: ft.hide(mNetworkFragment).hide(mScannerFragment).hide(mSettingsFragment).show(mMainFragment); break; case R.id.action_settings: ft.hide(mNetworkFragment).hide(mScannerFragment).hide(mSettingsFragment).show(mMainFragment); break; } ft.commit(); invalidateOptionsMenu(); return true; } @Override public void onNavigationItemReselected(@NonNull MenuItem item) { } @Override public void showProgressBar() { mScanningView.setVisibility(View.INVISIBLE); } @Override public void hideProgressBar() { mScanningView.setVisibility(View.INVISIBLE); } @Override public void onBackStackChanged() { } @Override public AndroidInjector<Fragment> supportFragmentInjector() { return mDispatchingAndroidInjector; } @Override public void onProvisionedMeshNodeSelected() { } @Override public void onNetworkReset() { mViewModel.resetMeshNetwork(); } }
My source fragment which is inflated by MainActivity is MainFragment.java:
/** * A simple {@link Fragment} subclass. * Activities that contain this fragment must implement the * {@link MainFragment.OnFragmentInteractionListener} interface * to handle interaction events. * Use the {@link MainFragment#newInstance} factory method to * create an instance of this fragment. */ public class MainFragment extends Fragment { // TODO: Rename parameter arguments, choose names that match // the fragment initialization parameters, e.g. ARG_ITEM_NUMBER private static final String ARG_PARAM1 = "param1"; private static final String ARG_PARAM2 = "param2"; // TODO: Rename and change types of parameters private String mParam1; private String mParam2; /*private OnFragmentInteractionListener mListener;*/ private MainFragmentListener mMainFragmentListener; BleMeshManager instBleMeshManager; @BindView(R.id.send) Button sendButton; @BindView(R.id.mesh) Button meshButton; private Unbinder unbinder; @OnClick(R.id.send) public void send() { instBleMeshManager.sendPdu(new byte[] {0x01, 0x02, 0x03, 0x04}); } @OnClick(R.id.mesh) public void mesh() { Intent meshActivityIntent = new Intent(getActivity(), MeshInteractionActivity.class); startActivity(meshActivityIntent); } public MainFragment() { // Required empty public constructor } /** * Use this factory method to create a new instance of * this fragment using the provided parameters. * * @param param1 Parameter 1. * @param param2 Parameter 2. * @return A new instance of fragment MainFragment. */ // TODO: Rename and change types and number of parameters public static MainFragment newInstance(String param1, String param2) { MainFragment fragment = new MainFragment(); Bundle args = new Bundle(); args.putString(ARG_PARAM1, param1); args.putString(ARG_PARAM2, param2); fragment.setArguments(args); return fragment; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (getArguments() != null) { mParam1 = getArguments().getString(ARG_PARAM1); mParam2 = getArguments().getString(ARG_PARAM2); } instBleMeshManager = new BleMeshManager(getActivity()); //onButtonPressed(); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout for this fragment View view = inflater.inflate(R.layout.fragment_main, container, false); ButterKnife.bind(this, view); // Inflate the layout for this fragment return view; } // TODO: Rename method, update argument and hook method into UI event public void onButtonPressed(/*Uri uri*/) { if (mMainFragmentListener != null) { mMainFragmentListener.hideProgressBar(/*uri*/); } } @Override public void onAttach(Context context) { super.onAttach(context); if (context instanceof MainFragmentListener) { mMainFragmentListener = (MainFragmentListener) context; } else { throw new RuntimeException(context.toString() + " must implement OnFragmentInteractionListener"); } } @Override public void onDetach() { super.onDetach(); mMainFragmentListener = null; } /*@Override public void onDestroyView() { super.onDestroyView(); unbinder.unbind(); }*/ /** * This interface must be implemented by activities that contain this * fragment to allow an interaction in this fragment to be communicated * to the activity and potentially other fragments contained in that * activity. * <p> * See the Android Training lesson <a href= * "http://developer.android.com/training/basics/fragments/communicating.html" * >Communicating with Other Fragments</a> for more information. */ /*public interface OnFragmentInteractionListener { // TODO: Update argument type and name void onFragmentInteraction(*//*Uri uri*//*); }*/ public interface MainFragmentListener { void showProgressBar(); void hideProgressBar(); } }
My destination Activity to which an Intent in my fragment of MainActivity jumps to is MeshInteractionActivity:
public class MeshInteractionActivity extends AppCompatActivity implements Injectable, HasSupportFragmentInjector, BottomNavigationView.OnNavigationItemSelectedListener, BottomNavigationView.OnNavigationItemReselectedListener, ScannerFragment.ScannerFragmentListener, FragmentManager.OnBackStackChangedListener, NetworkFragment.NetworkFragmentListener, DialogFragmentResetNetwork.DialogFragmentResetNetworkListener { private static final String TAG = MeshInteractionActivity.class.getSimpleName(); private static final String CURRENT_FRAGMENT = "CURRENT_FRAGMENT"; @Inject DispatchingAndroidInjector<Fragment> mDispatchingAndroidInjector; @Inject ViewModelProvider.Factory mViewModelFactory; @BindView(R.id.state_scanning) View mScanningView; private SharedViewModel mViewModel; private BottomNavigationView mBottomNavigationView; private NetworkFragment mNetworkFragment; private ScannerFragment mScannerFragment; private Fragment mSettingsFragment; @Override protected void onCreate(@Nullable final Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_mesh_interaction); ButterKnife.bind(this); final Toolbar toolbar = findViewById(R.id.toolbar); setSupportActionBar(toolbar); getSupportActionBar().setTitle(R.string.app_name); mViewModel = ViewModelProviders.of(this, mViewModelFactory).get(SharedViewModel.class); mNetworkFragment = (NetworkFragment) getSupportFragmentManager().findFragmentById(R.id.fragment_network); mScannerFragment = (ScannerFragment) getSupportFragmentManager().findFragmentById(R.id.fragment_scanner); mSettingsFragment = getSupportFragmentManager().findFragmentById(R.id.fragment_settings); mBottomNavigationView = findViewById(R.id.bottom_navigation_view); mBottomNavigationView.setOnNavigationItemSelectedListener(this); mBottomNavigationView.setOnNavigationItemReselectedListener(this); mViewModel.getProvisionedNodesLiveData().observe(this, provisionedNodesLiveData -> { invalidateOptionsMenu(); }); mViewModel.isConnected().observe(this, isConnected -> { if(isConnected != null) { invalidateOptionsMenu(); } }); if(savedInstanceState == null) { onNavigationItemSelected(mBottomNavigationView.getMenu().findItem(R.id.action_network)); } else { mBottomNavigationView.setSelectedItemId(savedInstanceState.getInt(CURRENT_FRAGMENT)); } } @Override public void onBackPressed() { super.onBackPressed(); Intent mainActivityIntent = new Intent(MeshInteractionActivity.this, MainActivity.class); startActivity(mainActivityIntent); } @Override public boolean onCreateOptionsMenu(final Menu menu) { if(!mViewModel.getProvisionedNodesLiveData().getProvisionedNodes().isEmpty()){ if(mNetworkFragment.isVisible()) { if (!mViewModel.isConenctedToMesh()) { getMenuInflater().inflate(R.menu.connect, menu); } else { getMenuInflater().inflate(R.menu.disconnect, menu); } } else if(mSettingsFragment.isVisible()){ getMenuInflater().inflate(R.menu.reset_network, menu); } else { return false; } } else { return false; } return true; } @Override public boolean onOptionsItemSelected(final MenuItem item) { final int id = item.getItemId(); switch (id) { case R.id.action_connect: final Intent scannerActivity = new Intent(this, ProvisionedNodesScannerActivity.class); scannerActivity.putExtra(ProvisionedNodesScannerActivity.NETWORK_ID, mViewModel.getNetworkId()); startActivity(scannerActivity); return true; case R.id.action_disconnect: mViewModel.disconnect(); return true; case R.id.action_reset_network: final DialogFragmentResetNetwork dialogFragmentResetNetwork = DialogFragmentResetNetwork. newInstance(getString(R.string.title_reset_network), getString(R.string.message_reset_network)); dialogFragmentResetNetwork.show(getSupportFragmentManager(), null); return true; } return false; } @Override public void onActivityResult(final int requestCode, final int resultCode, final Intent data) { super.onActivityResult(requestCode, resultCode, data); if(requestCode == Utils.PROVISIONING_SUCCESS){ if(resultCode == RESULT_OK){ final boolean result = data.getBooleanExtra("result", false); if(result){ mBottomNavigationView.setSelectedItemId(R.id.action_network); } } } } @Override public boolean onNavigationItemSelected(@NonNull MenuItem item) { final int id = item.getItemId(); final FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); switch (id) { case R.id.action_network: ft.show(mNetworkFragment).hide(mScannerFragment).hide(mSettingsFragment); break; case R.id.action_scanner: ft.hide(mNetworkFragment).show(mScannerFragment).hide(mSettingsFragment); break; case R.id.action_settings: ft.hide(mNetworkFragment).hide(mScannerFragment).show(mSettingsFragment); break; } ft.commit(); invalidateOptionsMenu(); return true; } @Override public void onNavigationItemReselected(@NonNull MenuItem item) { } @Override public void showProgressBar() { mScanningView.setVisibility(View.VISIBLE); } @Override public void hideProgressBar() { mScanningView.setVisibility(View.INVISIBLE); } @Override public void onBackStackChanged() { } @Override public AndroidInjector<Fragment> supportFragmentInjector() { return mDispatchingAndroidInjector; } @Override public void onProvisionedMeshNodeSelected() { } @Override public void onNetworkReset() { mViewModel.resetMeshNetwork(); } }
This activity too, has its own fragments but I guess the problem is where the Intent from 1st Activity fragment jumps to 2nd Activity.
Thanks in advance.