I have made an android bottom bar in activity and thought i'd use dagger2 to set listener for it. How ever I am confused as to how to get the getSupportFragmentManager() inside the listener class.
Here's what i'm trying
public class MainActivity extends AppCompatActivity {
#BindView(R.id.bottomNavigationView)
BottomNavigationView bottomNavigationView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
BottomBarComponent bottomBarComponent = DaggerBottomBarComponent.builder()
.bottomBarModule(new BottomBarModule(getSupportFragmentManager()))
.build();
}
}
The component part is
#Singleton
#Component(modules = {BottomBarModule.class})
public interface BottomBarComponent {
void inject(BottomNavigationListener listener);
}
The module is
#Module
public class BottomBarModule {
private FragmentManager manager;
public BottomBarModule(FragmentManager context) {
this.manager = context;
}
#Singleton
#Provides
public FragmentManager provideSupportManager(){
return manager;
}
}
And need to get fragmentsupportManager here
public class BottomNavigationListener implements BottomNavigationView.OnNavigationItemSelectedListener{
#Inject
FragmentManager manager;
public void BottomNavigationListener() {
//somehow need to get fragmentSupportManager here
}
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
return true;
}
}
How do I do this?
You need to share instance of BottomBarComponent with your BottomNavigationListener and invoke inject() method on it.
BottomNavigationListener.class
...
public void BottomNavigationListener(BottomBarComponent injector) {
//somehow need to get fragmentSupportManager here
injector.inject(this); // now manager field must be filled
}
...
MainActivity.class
...
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
BottomBarComponent bottomBarComponent = DaggerBottomBarComponent.builder()
.bottomBarModule(new BottomBarModule(getSupportFragmentManager()))
.build();
// pass BottomBarComponent to BottomNavigationListener for injecting FragmentManager
BottomNavigationListener listener = new BottomNavigationListener(bottomBarComponent);
}
...
But very strange thing you're trying to do. It's all equivalent to passing FragmentManager directly to BottomNavigationListener without using Dagger.
Related
I am new to android architecture components and I am little confused with viewmodel. I am building an app which get a list of items from the server and display as a list in the layout. I have implemented the network call in the Repository class.
Repository.java:
//Get list of top rated movies
public LiveData<NetworkResponse> getTopRatedMovies() {
final MutableLiveData<NetworkResponse> result = new MutableLiveData<>();
ApiService api = retrofit.create(ApiService.class);
Call<MovieData> call = api.getTopRateMovies("api_key");
call.enqueue(new Callback<MovieData>() {
#Override
public void onResponse(Call<MovieData> call, Response<MovieData> response) {
result.postValue(new NetworkResponse(response.body()));
}
#Override
public void onFailure(Call<MovieData> call, Throwable t) {
Log.e(TAG, t.getLocalizedMessage());
result.postValue(new NetworkResponse(t));
}
});
return result;
}
Now in the ViewModel class I am doing this:
public class MovieListViewModel extends ViewModel {
public LiveData<NetworkResponse> result, topRatedMovies;
public LiveData<List<MovieEntity>> favoriteMovies;
private Repository repository;
public MovieListViewModel() {
repository = new Repository(MyApplication.getInstance());
}
public void getTopRatedMovieList() {
topRatedMovies = repository.getTopRatedMovies();
}
}
Now in the MainActivity.java:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
((MyApplication) getApplication()).getComponent().inject(this);
movieListViewModel = ViewModelProviders.of(this).get(MovieListViewModel.class);
recyclerView.setHasFixedSize(true);
recyclerView.setLayoutManager(new GridLayoutManager(this, 2));
adapter = new MovieListAdapter(this);
movieListViewModel.getTopRatedMovieList();
observeTopRatedMovies();
}
private void observeTopRatedMovies() {
movieListViewModel.topRatedMovies.observe(this, new Observer<NetworkResponse>() {
#Override
public void onChanged(#Nullable NetworkResponse networkResponse) {
if (networkResponse.getPostData() != null) {
Log.e(TAG, "Successful");
topRatedData = networkResponse.getPostData();
adapter.addData(networkResponse.getPostData().getResults());
recyclerView.setAdapter(adapter);
} else {
Log.e(TAG, "failure");
}
}
});
}
Now everything works fine and I am able to see the list. But if I rotate the phone the viewmodel makes the network call again. How can I avoid the network call again on screen orientation change?
You can initialize live data only once. That should be enough:
public class MovieListViewModel extends ViewModel {
public LiveData<NetworkResponse> result, topRatedMovies;
public LiveData<List<MovieEntity>> favoriteMovies;
private Repository repository;
public MovieListViewModel() {
repository = new Repository(MyApplication.getInstance());
topRatedMovies = repository.getTopRatedMovies();
}
}
I suggest you to use headless-fragment design pattern. A headless fragment is a fragment that retain his configuration and it doesn't inflate any xml. If you rotate your app the fragment continue with his logic and configuration and is very useful when you have to do async task or async call (like you in retrofit)
define your fragment:
public class YourFragment extends Fragment {
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true); // <--------- the fragment retain his configuration
}
public void yourLogic(){
// do your logic
}
}
in your MainActivity class create the fragment or get the istance fragment if it already exists:
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
_yourHeadLessFragment= (YourFragment) getSupportFragmentManager().findFragmentByTag(HEADLESS_FRAGMENT);
if (_yourHeadLessFragment== null) {
_yourHeadLessFragment= new YourFragment();
_yourHeadLessFragment.setListener(this); // if you want a callback
getSupportFragmentManager().beginTransaction().add(_yourHeadLessFragment, HEADLESS_FRAGMENT).commit();
}
else{
_yourHeadLessFragment.setListener(this); // refresh the callbacks if a rotation happened
}
}
}
you can put network call in the init block of ViewModel
I am trying to inject my MainActivity into the Fragment. I have an Interface that is implemented in my MainActivity that will listen to events from the Fragment. So I want to Inject the MainActivity and call the event listener on it.
I have tried doing the following but has failed to do so. Just displaying the code snippets.
interface
public interface RecipeItemListener {
void onRecipeItem();
}
MainActivity that implements the interface
public class MainActivity extends AppCompatActivity implements RecipeItemListener {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if(savedInstanceState == null) {
getSupportFragmentManager()
.beginTransaction()
.add(R.id.main_fragment_container, RecipeListView.newInstance(), RecipeListView.TAG)
.commit();
}
}
#Override
public void onRecipeItem() {
Timber.d("onRecipeItem");
}
}
My Module that provides the MainActivity
#Module
public class RecipeListModule {
private MainActivity mainActivity;
public RecipeListModule(MainActivity mainActivity) {
this.mainActivity = mainActivity;
}
#RecipeListScope
#Provides
public RecipeItemListener providesRecipeListMainActivity() {
return mainActivity;
}
}
My main Component
#Singleton
#Component(modules = {
AndroidModule.class,
NetworkModule.class})
public interface BusbyBakingComponent {
RecipeListComponent add(RecipeListModule recipeListModule);
}
My SubComponent
#RecipeListScope
#Subcomponent(modules = {RecipeListModule.class})
public interface RecipeListComponent {
void inject(RecipeListView target);
}
My Application class
public class BusbyBakingApplication extends Application {
private BusbyBakingComponent applicationComponent;
private RecipeListComponent recipeListComponent;
#Override
public void onCreate() {
super.onCreate();
applicationComponent = createApplicationComponent();
}
public BusbyBakingComponent createApplicationComponent() {
return DaggerBusbyBakingComponent.builder()
.networkModule(new NetworkModule())
.androidModule(new AndroidModule(BusbyBakingApplication.this))
.build();
}
public BusbyBakingComponent getApplicationComponent() {
return applicationComponent;
}
public RecipeListComponent createRecipeListComponent(MainActivity activity) {
recipeListComponent = applicationComponent.add(new RecipeListModule(activity));
return recipeListComponent;
}
public void releaseRecipeListComponent() {
recipeListComponent = null;
}
}
And in My fragment this is how I am trying to inject:
#Inject MainActivity mainActivity;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
((BusbyBakingApplication)getActivity().getApplication())
.createRecipeListComponent((MainActivity)getActivity())
.inject(RecipeListView.this);
}
I keep getting the following error:
Error:(14, 8) error: [me.androidbox.busbybaking.di.RecipeListComponent.inject(me.androidbox.busbybaking.recipieslist.RecipeListView)] me.androidbox.busbybaking.recipieslist.MainActivity cannot be provided without an #Inject constructor or from an #Provides- or #Produces-annotated method.
me.androidbox.busbybaking.recipieslist.MainActivity is injected at
me.androidbox.busbybaking.recipieslist.RecipeListView.mainActivity
me.androidbox.busbybaking.recipieslist.RecipeListView is injected at
me.androidbox.busbybaking.di.RecipeListComponent.inject(target)
Many thanks for any suggestions.
If you have a look at your module
#RecipeListScope
#Provides
public RecipeItemListener providesRecipeListMainActivity() {
return mainActivity;
}
You provide the interface (which is good) and not MainActivity (the implementation).
Since you request MainActivity
#Inject MainActivity mainActivity;
You receive this error:
MainActivity cannot be provided [...]
because you only provide RecipeItemListener.
Switch your code from requiring MainActivity in RecipeListView to
#Inject RecipeItemListener recipeListener;
and it should work, if the rest of your setup is correct.
You can access activity in Fragment using getActivity() and cast it to your interface listener like this
((RecipeItemListener)getActivity()).doSomethingOnListener()
much simpler, without any unnecessary injections
Im using MVP and RxJava similar to google-samples repo.
And I would like to ask how to correctly handle screen orientation change.
There is another strategy that enables saving presenter state and also Observable's state: retain Fragment. This way you omit standard Android way of saving data into Bundle (which enables only to save simple variables and not the state of network requests.)
Activity:
public class MainActivity extends AppCompatActivity implements MainActivityView {
private static final String TAG_RETAIN_FRAGMENT = "retain_fragment";
MainActivityPresenter mPresenter;
private MainActivityRetainFragment mRetainFragment;
#Override
protected void onCreate(Bundle savedInstanceState) {
initRetainFragment();
initPresenter();
}
private void initRetainFragment() {
FragmentManager fm = getSupportFragmentManager();
mRetainFragment = (MainActivityRetainFragment) fm.findFragmentByTag(TAG_RETAIN_FRAGMENT);
if (mRetainFragment == null) {
mRetainFragment = new MainActivityRetainFragment();
fm.beginTransaction().add(mRetainFragment, TAG_RETAIN_FRAGMENT).commit();
}
}
private void initPresenter() {
mPresenter = mRetainFragment.getPresenter();
mRetainFragment.retainPresenter(null);
if (mPresenter == null) {
mPresenter = new MainActivityPresenter();
}
mPresenter.attachView(this);
}
#Override
protected void onDestroy() {
super.onDestroy();
if (!isFinishing()) {
mRetainFragment.retainPresenter(mPresenter);
return;
}
mPresenter.detachView();
mPresenter = null;
}
}
Retain Fragment:
public class MainActivityRetainFragment extends Fragment {
private MainActivityPresenter presenter;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
public void retainPresenter(MainActivityPresenter presenter) {
this.presenter = presenter;
}
public MainActivityPresenter getPresenter() {
return presenter;
}
}
Notice the way activity lifecycle events are handled. When the Activity is created, retain Fragment is added to the backstack and on lifecycle events it is restored from backstack. retain Fragment does not have any view, it is just a holder for presenter during configuration changes. Notice the main invocation that enables restoring exactly the same fragment (and it's content) from backstack:
setRetainInstance(true)
If you are concerned about memory leaks: every time the presenter is restored presenter's view is restored:
mPresenter.attachView(this);
So the previous Activity reference is replaced by new one.
More about such handling of configuration changes here here
I handled by encapsulating view's state in specific ViewState class in presenter, and it is easy to test.
public interface BaseViewState {
void saveState(#NonNull Bundle outState);
void restoreState(#Nullable Bundle savedInstanceState);
}
class HomeViewState implements BaseViewState {
static final long NONE_NUM = -1;
static final String STATE_COMIC_NUM = "state_comic_num";
private long comicNum = NONE_NUM;
#Inject
HomeViewState() {
}
#Override
public void saveState(#NonNull Bundle outState) {
outState.putLong(STATE_COMIC_NUM, comicNum);
}
#Override
public void restoreState(#Nullable Bundle savedInstanceState) {
if (savedInstanceState != null) {
comicNum = savedInstanceState.getLong(STATE_COMIC_NUM, NONE_NUM);
}
}
long getComicNumber() {
return comicNum;
}
void setComicNum(long comicNum) {
this.comicNum = comicNum;
}
}
get/set values from viewState in presenter, this helps to keep it updated, as well as presenter stateless.
public class HomePresenter implements HomeContract.Presenter {
private HomeViewState viewState;
HomeViewState getViewState() {
return viewState;
}
#Override
public void loadComic() {
loadComic(viewState.getComicNumber());
}
...
}
in Activity as View should initiate call to save and restore.
public class MainActivity extends BaseActivity implements HomeContract.View {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
homePresenter.getViewState().restoreState(savedInstanceState);
}
#Override
public void onSaveInstanceState(Bundle outState, PersistableBundle outPersistentState) {
super.onSaveInstanceState(outState, outPersistentState);
homePresenter.getViewState().saveState(outState);
}
...
}
I am attempting to add Dagger 2 to my Android Project.
My application have following screen
Login extends Base activity
Navigation Activity extends Base activity
MW Activity extents Navigation Activity
Presenter Injection is working fine in Login and navigation activity where as in MW activity it return null
Butter Knife is also not working in MW Activity where as working fine in other activities
Following are my classes
Application class
public class abcApplication extends Application {
ApplicationComponent mApplicationComponent;
#Override
public void onCreate() {
super.onCreate();
mApplicationComponent = DaggerApplicationComponent.builder()
.applicationModule(new ApplicationModule(this))
.build();
mApplicationComponent.inject(this);
}
public static abcApplication get(Context context) {
return (abcApplication) context.getApplicationContext();
}
public ApplicationComponent getComponent() {
return mApplicationComponent;
}
// Needed to replace the component with a test specific one
public void setComponent(ApplicationComponent applicationComponent) {
mApplicationComponent = applicationComponent;
}
}
Base activity
public class BaseActivity extends AppCompatActivity {
private ActivityComponent mActivityComponent;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
public ActivityComponent activityComponent() {
if (mActivityComponent == null) {
mActivityComponent = DaggerActivityComponent.builder()
.activityModule(new ActivityModule(this))
.applicationComponent(abcApplication.get(this).getComponent())
.build();
}
return mActivityComponent;
}
}
Navigation Activity
public class NavigationActivity extends BaseActivity implements NavigationView {
#Inject
DataClient mDataClient;
#Bind(R.id.drawer_layout)
protected DrawerLayout mDrawerLayout;
#Bind(R.id.navList)
ExpandableListView mExpandableListView;
private ActionBarDrawerToggle mDrawerToggle;
private String mActivityTitle;
private ExpandableListAdapter mExpandableListAdapter;
private List<String> mExpandableListTitle;
private Map<String, List<String>> mExpandableListData;
private Map<String, String> activityMap;
private int lastExpandedPosition = -1;
#Inject
NavigationPresenter navigationPresenter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_navigation);
activityComponent().inject(this);
ButterKnife.bind(this);
navigationPresenter.attachView(this);
}
#Override
protected void onDestroy() {
super.onDestroy();
navigationPresenter.detachView();
}
}
MW Activity
public class MWActivity extends NavigationActivity implements MWView{
private MWPagerAdapter mMWPagerAdapter;
#Inject
MWPresenter MWPresenter;
private ViewPager mViewPager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LayoutInflater inflater = (LayoutInflater) this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
DrawerLayout mDrawer = (DrawerLayout) findViewById(R.id.drawer_layout);
ButterKnife.bind(this);
MWPresenter.attachView(this);
MWPresenter.getMarketData();
}
}
Logcat :
FATAL EXCEPTION: main
Process: com.abc.xyz, PID: 21542
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.abc.xyz/com.abc.trading.xyz.ui.main.mw.view.MWActivity}: java.lang.NullPointerException
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2318)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2396)
#PreActivity
#Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class)
public interface ActivityComponent {
void inject(LoginActivity loginActivity);
void inject(NavigationActivity navigationActivity);
void inject(MWActivity mWActivity);
void inject(MWTabFragment mWTabFragment);
void inject(MWDetailsActivity mWDetailsActivity);
}
You have 2 issues regarding super- / sub types at hand.
Butterknife does not support injection to super types
Dagger does not support injection to sub types
As already pointed out, to solve 2. you would need to call inject in your MWActivity, and to use Butterknife you need to use a ViewHolder pattern within your super class to bind / inject the fields, since it will only inject MWActivity and not NavigationActivity.
Activity Component was not injected activityComponent().inject(this);
in MW Activity
public class MWActivity extends NavigationActivity implements MWView{
private MWPagerAdapter mMWPagerAdapter;
#Inject
MWPresenter MWPresenter;
private ViewPager mViewPager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LayoutInflater inflater = (LayoutInflater) this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
activityComponent().inject(this);
DrawerLayout mDrawer = (DrawerLayout) findViewById(R.id.drawer_layout);
ButterKnife.bind(this);
MWPresenter.attachView(this);
MWPresenter.getMarketData();
}
}
ActivityComponent (Base Activity)
public class BaseActivity extends AppCompatActivity {
private ActivityComponent mActivityComponent;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
public ActivityComponent activityComponent() {
if (mActivityComponent == null) {
mActivityComponent = DaggerActivityComponent.builder()
.activityModule(new ActivityModule(this))
.applicationComponent(OmsApplication.get(this).getComponent())
.build();
}
return mActivityComponent;
}
}
I have a custom class Fragment AFragment that has an injected attribute : AController controller.
The problem is that when I call this : controller.onStart() --> controller is null.
The code :
Class AFragment :
public class AFragment extends Fragment {
#Inject
AController controller;
#Override
public void onStart() {
super.onStart();
controller.onStart();
}
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
controller.onCreate();
}
}
Class AController :
public class AController {
private final DataInteractor dataInteractor;
#Inject
public AController(DataInteractor dataInteractor){
this.dataInteractor = dataInteractor;
}
public void onCreate(){
}
public void onStart(){
}
}
The only thing you need is create a component and inject AFragment into it.
#Singleton
#Component
public interface ApplicationComponent {
void inject(AFragment fragment);
}
Because in AController class you make a Constructor inject so you don't need to make a module for your component.
And also you need to create the component when application start. So just init it in your Application extended class.
public class DemoApplication extends Application {
private ApplicationComponent mComponent;
#Override
public void onCreate() {
super.onCreate();
mComponent = DaggerApplicationComponent.builder()
.build();
}
public ApplicationComponent getComponent() {
return mComponent;
}
}
The last step is what i say before, inject AFragment into the component.
public class AFragment extends Fragment {
#Inject
AController controller;
#Override
public void onStart() {
super.onStart();
controller.onStart();
}
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
((DemoApplication) getApplication()).getComponent().inject(this);
controller.onCreate();
}
}