This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

Unable to call the MainActivity of Android-nRF-Mesh-Library from my Activity in Android for my version of nRF Mesh Provisioner app

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.

Parents
  • Hello,

    From what I see the issue you have is related to dependency injection. Any new activity you add has to contributed towards the AndroidInjector. If you look at the nRF Mesh app on Github, the ActivitiesModule.java class contributes all activities that implements Injectable interface. Therefore you will have to contribute your activity from the ActivitiesModule similarly. Also if there is no Fragments involved in your MeshInteractionActivity you should avoid implementing HasSupportFragmentInjector. I will close the existing github issue and move it here.

Reply
  • Hello,

    From what I see the issue you have is related to dependency injection. Any new activity you add has to contributed towards the AndroidInjector. If you look at the nRF Mesh app on Github, the ActivitiesModule.java class contributes all activities that implements Injectable interface. Therefore you will have to contribute your activity from the ActivitiesModule similarly. Also if there is no Fragments involved in your MeshInteractionActivity you should avoid implementing HasSupportFragmentInjector. I will close the existing github issue and move it here.

Children
Related