I'm trying to create Login activity in app based on MVP pattern. User input will be sent to the database using Cursor Loader.
1# Is it the correct way to pass user input from view to model, using presenter(and there's validation included) and in model provide it using other db provider class?
public class LoginActivityModel extends AppCompatActivity implements LoginActivityMVP.Model, LoaderManager.LoaderCallbacks<Cursor> {
private ApplicationModule applicationModule;
#Override
public void sendUserToDb(String firstName, String secondName, Uri currentUserUri) {
ContentValues values = new ContentValues();
String date = new SimpleDateFormat("dd-MM-yyyy").format(new Date());
values.put(UserEntry.COLUMN_USERNAME, firstName);
values.put(UserEntry.COLUMN_PASSWORD, secondName);
values.put(UserEntry.COLUMN_DATE, date);
if (currentUserUri != null) {
getSupportLoaderManager().initLoader(0, null, this);
}
Uri newUri = applicationModule.provideContext().getContentResolver().insert(UserEntry.CONTENT_URI, values);
if (newUri == null) {
Toast.makeText(applicationModule.provideContext(), "failed",
Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(applicationModule.provideContext(), "succeed",
Toast.LENGTH_SHORT).show();
}
}
2a# If yes, how can I inject context from my view? I tried, as you can see above, to get context injected in root appliaction module but it doesn't work. I've got null context from App. Any other way that I found here, on stackoverflow didn't work.
#Module
public class ApplicationModule {
private Application application;
public ApplicationModule(Application application) {
this.application = application;
}
#Provides
#Singleton
public Context provideContext() {
return application;
}
}
Also, my App
public class App extends Application {
private ApplicationComponent component;
public LoginActivity login;
#Override
public void onCreate() {
super.onCreate();
component = DaggerApplicationComponent.builder()
.applicationModule(new ApplicationModule(this))
.loginActivityModule(new LoginActivityModule())
.build();
}
public ApplicationComponent getComponent() {
return component;
}
}
2b# If no, how should I implement Cursor Loader into MVP pattern based appliaction? Or maybe is it a bad practice?
Related
I am trying to use dagger2 and database in my android application. I can not access the database class with the inject operation. The Database db object I created is returning null. I am new on dagger2. Thanks for any help.
This is my database class.
public class Database extends SQLiteOpenHelper {
#Inject
public Database(#ApplicationContext Context context) {
super(context, Constants.DATABASE_NAME, null, Constants.DATABASE_VERSION);
}
public void createTables(){..}
public void clearAllTables() {...}
public void dropTable(SQLiteDatabase db, String tableName){
db.execSQL("DROP TABLE IF EXISTS " + tableName);
}
#Override
public void onCreate(SQLiteDatabase db) {.. } .. }
This is my module.
#Module abstract public class DatabaseModule {
private Application application;
public DatabaseModule(Application application) {
this.application = application;
}
#Provides
#ApplicationContext
Context provideContext() {
return application;
}}
And component
#ActivityScope #Subcomponent public interface DatabaseSubcomponent {
#ApplicationContext
Context getContext();
Database getDb(); }
Db object returns null as follows :
#Inject
Database db;
I will be grateful if you tell me where I made a mistake. Thanks in advance.
define a scope annotator for yourself #DatabaseScope.java
#Scope
#Retention(RetentionPolicy.RUNTIME)
public #interface DatabaseScope {
}
And then the DatabaseModule.java should provide context and database objects which is your module(during injection of database object dagger automatically injects context for you from the object graph)
#Module
public class DatabaseModule {
private Application application;
public DatabaseModule(Application application){
this.application = application;
}
#Provides
#DatabaseScope
Context provideContext(){
return application;
}
#Provides
#DatabaseScope
Database provideDatabase(Context context){
return new Database(context);
}
}
And then the DatabaseComponent.java . You need not annotate this as a subcomponent
#DatabaseScope
#Component(modules = {DatabaseModule.class})
public interface DatabaseComponent {
void inject(MainActivity mainActivity);
}
and then your database.java
public class Database extends Object {
public Database(Context context) {
Log.e("database creted"," ");
}
String getmessage(){
return "jenison";
}
}
and finally inject this in your activity
public class MainActivity extends AppCompatActivity {
#Inject
Database db;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
DatabaseComponent component=DaggerDatabaseComponent.builder().databaseModule(new DatabaseModule(getApplication())).build();
component.inject(this);
Log.e("test"," "+db.getmessage());
}
}
I am trying MVP pattern with TDD.
I have the following contract for Model, View And Presenter
Contract Class
interface GithubContract {
interface View {
void displayUsers(List<GurkhaComboDTO> userList);
}
interface Model {
void getUsersAndPromptPresenter(String userName, Presenter presenter);
}
interface Presenter {
void searchUsers(String userName);
void loadUsers(List<GithubUserDTO> userList);
}
}
I am trying to unit test the presenter logic like this :
Test Class
#RunWith(MockitoJUnitRunner.class)
public class GithubPresenterWithMockitoTest {
#Mock
GithubContract.Model mockedModel;
#Test
public void shouldDisplayUsersToScreen() {
//given
final GithubContract.View view = new MockView(); // I have created the mock myself for the view this time.
final GithubContract.Presenter presenter = new GithubPresenter(view, mockedModel);
***********************************************************
// I do not know what to write here
****************************************************
presenter.searchUsers("");
Assert.assertEquals(true, ((MockView) (view)).enoughItems);
}
}
My MockView / VIEW class looks like this :
This is -> Mock class
class MockView implements GithubContract.View {
boolean enoughItems = false;
#Override
public void displayUsers(List<GurkhaComboDTO> userList) {
enoughItems = true;
}
}
My PRESENTER implementation of contract is like this ..
This is -> Real Class
class GithubPresenter implements GithubContract.Presenter {
private GithubContract.View view;
private GithubContract.Model model;
GithubPresenter(GithubContract.View view, GithubContract.Model model) {
this.view = view;
this.model = model;
}
#Override
public void searchUsers(String userName) {
model.getUsersAndPromptPresenter(userName, this);
}
#Override
public void loadUsers(List<GithubUserDTO> data) {
if (data != null) {
if (!data.isEmpty()) {
view.displayUsers(users);
}
}
}
I have the MODEL class Implementation like this :
This is -> Real Class
public class GithubModel implements Model {
#Inject
GithubAPIService apiService;
private Call<GithubUserListDTO> userListCall;
private Context context;
GithubModel(Context context) {
this.context = context;
apiService = Util.getAPIService(); // I am using dagger, retrofit and okhttp3 with GSON to get Objects directly from network call
}
#Override
public void getUsersAndPromptPresenter(final String userName, final GithubContract.Presenter presenter) {
userListCall = apiService.searchGitHubUsers(userName);
if(Util.isInternetConnected(context)) {
userListCall.enqueue(new Callback<GithubUserListDTO>() {
#Override
public void onResponse(Call<GithubUserListDTO> call, Response<GithubUserListDTO> response) {
try {
presenter.loadUsers(response.body().getList());
} catch (Exception ignored) {
Util.log(ignored.getMessage());
}
}
#Override
public void onFailure(Call<GithubUserListDTO> call, Throwable t) {
}
});
}else {
Util.log("No Internet");
}
}
}
Now the real problem part:
I was successfully able to test the presenter with the mock of GithubContract.Model myself, But I want to use Mockito to mock the Model but as my getUsersAndPromptPresenter() method is abstract, returns void, takes parameters and calls back to presenter from an Inner class inside the method.
How can I mock my Model? If I need to bring some change in architecture in order to be able to make it testable, then please suggest it.
You shouldn't pass presenter to Model, Model and Presenter shouldn't be tightly coupled because it prevents model classes from being reusable. Instead provide succesfull and error callbacks(or a composite object that contains both these callbacks). And then you will be able to capture that callback with mockito and call the required one. Also it's very common today to use RxJava, it makes it easier to mock Model classes.
And here is a general good practice: you should avoid to use And/Or words in method names because it indicates that the method is doing more than one thing which is bad
I'm trying to refactor my app using MVP pattern and Dagger 2 for Dependency Injection.
I Create module that provides Application Context and I want get Context to get SharedPreferences on Model Layer.
I inject Context on Presenter layer and it's working with SharedPreference, but when I move to Model layer, Dagger inject a null value on Context variable.
Inject
#Inject
public Context mContext;
App Module
AppModule provides Application Context
#Module
public class AppModule {
private App app;
public AppModule(App app){
this.app = app;
}
#Provides
public Context providesApp(){
return app.getApplicationContext();
}
}
Application
public class App extends Application {
private AppComponent appComponent;
#Override
public void onCreate() {
super.onCreate();
appComponent = DaggerAppComponent.builder()
.appModule(new AppModule(this))
.mainModule(new MainModule())
.build();
}
public AppComponent getAppComponent(){
return appComponent;
}
}
App Component
#Component(modules = {AppModule.class,MainModule.class})
public interface AppComponent {
void inject(MainActivity activity);
void inject(LoginActivity activity);
}
Main Module
#Module
public class MainModule {
#Provides
public MainContract.Model providesMainModel(){
return new MainModel();
}
#Provides
public LoginContract.Model providesLoginModel(){
return new LoginModel();
}
}
Since you have the Context in your App Module, you can add the Context as parameter to LoginModel constructor.
#Provides
public LoginContract.Model providesLoginModel(Context contect){
return new LoginModel(contect);
}
#Provides
public LoginContract.Presenter providesLoginPresenter(LoginContract.Model model){
return new LoginPresenter(model);
}
But bear in mind that you must use android packages only on your Activities and Fragments for easy testing. So your approach is wrong.
EDIT
Well for me, the best option is to create a class for SharedPreferences like this
public class Preferences {
private final SharedPreferences preferences;
private String key;
private String defaultValue;
public StringPreferences(#NonNull SharedPreferences preferences) {
this.preferences = preferences;
}
#Nullable
public String get(String mykey) {
return preferences.getString(mykey, defaultValue);
}
public void set(#NonNull String mykey, #Nullable String value) {
preferences.edit().putString(mykey, value).apply();
}
// same way for integers, longs, doubles and booleans
public boolean isSet() {
return preferences.contains(key);
}
public void delete() {
preferences.edit().remove(key).apply();
}
}
Then in App Module I Provide the SharedPreferences
#Provides
#Singleton
public SharedPreferences provideSharedPreferences(Context context) {
return PreferenceManager.getDefaultSharedPreferences(context);
}
#Provides
#Singleton
public Preferences providePreferences(SharedPreferences prefs) {
return new Preferences(prefs);
}
Finally in Main Module
#Provides
public LoginContract.Model providesLoginModel(Preferences prefs){
return new LoginModel(prefs);
}
#Provides
public LoginContract.Presenter providesLoginPresenter(LoginContract.Model model){
return new LoginPresenter(model);
}
Sorry for the long question / code and my english skills :)
I want to use Dagger-2 in my new android app. I have no experience with Dagger-2 or other dependency libraries. I think i misunderstand something...
Here are the relevant application. If the activity "OverviewActivity" starts it injects the "OverviewPresenter" and so on.. (OverviewActivity->OverviewPresenter->CategoriesPovider->DBOpenHelper->DBDefaults).
At the first start the class DBDefaults should prefill the database with some default values. At the time it should access the resources files i get a NPE. Can anybody tell me why this happens? I tried to use the "Log" tool to check if any needed variable is null but everything looks fine. Gets the DBDefaults class the wrong context?
Finally the method throws a NPE
context.getResources().getStringArray(R.array.category_colors);
But here is my code.
AppComponent.java
#Singleton
#Component(modules = AppModule.class)
public interface AppComponent {
Context getContext();
Application getApplication();
DBOpenHelper getDBOpenHelper();
}
AppModule.java
#Module
public class AppModule {
private final Manager app;
public AppModule(Manager app) {
this.app = app;
}
#Singleton
#Provides
public Application provideApplication() {
return app;
}
#Singleton
#Provides
public Context provideContext() {
return app.getApplicationContext();
}
#Singleton
#Provides
public DBOpenHelper provideDBOpenHelper() {
return new DBOpenHelper(app);
}
}
OverviewComponent.java
#ActivityScope
#Component(dependencies = AppComponent.class)
public interface OverviewComponent {
void inject(OverviewActivity activity);
OverviewPresenter getOverviewPresenter();
}
OverviewPresenter.java
public class OverviewPresenter extends MvpBasePresenter<OverviewView> {
#Inject
public OverviewPresenter(CategoriesProvider provider) {
Log.d("OverviewPresenter", "presenter..");
}
}
CategoriesProvider.java
public class CategoriesProvider extends BaseProvider {
#Inject
public CategoriesProvider(DBOpenHelper dbOpenHelper) {
super(dbOpenHelper);
// Just testing...
dbOpenHelper.getReadableDatabase();
}
}
DBOpenHelper.java
public class DBOpenHelper extends SQLiteOpenHelper {
private static final String db = "manager";
private static final int version = 1;
private Context context;
#Inject
public DBOpenHelper(Context context) {
super(context, db, null, version);
this.context = context;
Log.d("DBOpenHelper", "database...");
if (context == null) {
Log.d("DBOpenHelper", "wtf...");
}
}
#Override
public void onCreate(SQLiteDatabase db) {
// db structure
db.execSQL(CategoriesTable.getCreateTableQuery());
db.execSQL(UsersTable.getCreateTableQuery());
// default values
DBDefaults defaults = new DBDefaults(db, context);
defaults.insertSystemUser();
defaults.insertCategories();
}
}
DBDefaults.java
public class DBDefaults {
private SQLiteDatabase db;
private Context context;
public DBDefaults(SQLiteDatabase db, Context context) {
this.db = db;
this.context = context;
if (context == null) {
Log.d("DBDefaults", "wtf... in Defaults");
}
}
public void insertCategories() {
String[] test = context.getResources().getStringArray(R.array.category_colors);
for (String item : test) {
Log.d("Categories", item);
}
// NPE ?!?!?!
}
}
EDIT
My Manger class. Here I'm storing my app component and init my AppModule
public class Manager extends Application {
private AppComponent appComponent;
#Override
public void onCreate() {
super.onCreate();
appComponent = DaggerAppComponent.builder().appModule(new AppModule(this)).build();
}
public AppComponent getAppComponent() {
return appComponent;
}
}
My Activity
public class OverviewActivity extends MvpActivity<OverviewView, OverviewPresenter> {
public OverviewComponent overviewComponent;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_navigation);
}
#Override
public void inject() {
Log.d("Activity", "inject");
overviewComponent = DaggerOverviewComponent.builder()
.appComponent(((MoneyManager)getApplication()).getAppComponent())
.build();
overviewComponent.inject(this);
}
#Override
public OverviewPresenter getPresenter() {
Log.d("Activity", "getPresenter");
return overviewComponent.getOverviewPresenter();
}
}
If CategoriesProvider is a ContentProvider, you might get null when calling Application.getApplicationContext(). So the context that you're injecting could be null. You can verify this in AppModule. Try logging the result before returning it. Note that Application.getApplicationContext() normally returns the same instance, so you could just return app; instead of what you currently have.
So, a bit of context. I'm using Dagger2, Retrofit and RxAndroid and structuring my app using an MVP architecture.
For now, all I'm doing is making a network request to the API a retrieving some information as soon as my main activity starts. I'm trying to persist my presenters through configuration changes to avoid making a new http request every time I rotate my screen.
MainActivity.java
public class MainActivity extends AppCompatActivity implements ForecastView {
#Inject
Presenter forecastPresenter;
private TextView text;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
text = (TextView) findViewById(R.id.weather);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
initializeDependencies();
initializePresenter();
}
private void initializeDependencies() {
DaggerWeatherApiComponent.builder()
.build().inject(this);
}
private void initializePresenter() {
forecastPresenter.attachView(this);
forecastPresenter.onCreate();
}
WeatherApiComponent.java
#Component(modules = {EndpointsModule.class})
#Singleton
public interface WeatherApiComponent {
void inject(MainActivity context);
}
EndpointsModule.java
#Module #Singleton
public class EndpointsModule {
#Provides
#Singleton
WeatherEndpoints provideEndpoints() {
Retrofit retrofit = new Retrofit.Builder()
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.client(new OkHttpClient())
.baseUrl("http://api.openweathermap.org/data/2.5/")
.build();
return retrofit.create(WeatherEndpoints.class);
}
#Provides
#Singleton
Repository providesRepository(RestRepository repository) {
return repository;
}
#Provides
#Singleton
Presenter providesPresenter(ForecastPresenter presenter) {
return presenter;
}
}
RestRespository
public class RestRepository implements Repository {
private WeatherEndpoints endpoints;
static final String API_KEY = "xxxxxxxxxxxxxxxxxxxxx";
#Inject
public RestRepository(WeatherEndpoints endpoints) {
this.endpoints = endpoints;
}
public Observable<Current> getCurrentWeather(String cityName) {
return endpoints.getCurrent(cityName, API_KEY);
}
public Observable<com.feresr.rxweather.models.List> getForecast(String cityName) {
return endpoints.getForecast(cityName, API_KEY).flatMap(new Func1<FiveDays, Observable<com.feresr.rxweather.models.List>>() {
#Override
public Observable<com.feresr.rxweather.models.List> call(FiveDays fiveDays) {
return Observable.from(fiveDays.getList());
}
});
}
}
ForecastPresenter.java
public class ForecastPresenter implements Presenter {
private GetForecastUseCase useCase;
private Subscription forecastSubscription;
private ArrayList<List> lists;
private ForecastView forecastView;
#Inject
public ForecastPresenter(GetForecastUseCase forecastUseCase) {
this.useCase = forecastUseCase;
lists = new ArrayList<>();
}
#Override
public void onStop() {
if (forecastSubscription.isUnsubscribed()) {
forecastSubscription.unsubscribe();
}
}
#Override
public void attachView(View v) {
forecastView = (ForecastView) v;
}
#Override
public void onCreate() {
if (lists.isEmpty()) {
forecastSubscription = useCase.execute().subscribe(new Action1<List>() {
#Override
public void call(List list) {
lists.add(list);
forecastView.addForecast(list.getWeather().get(0).getMain());
}
});
} else {
forecastView.addForecast(lists.get(0).toString());
}
}
The constructor on this class (presenter) keeps calling itself as I rotate my Acitivity. I've annotated with #Singleton most of my classes. I don't know what else to do.
EDIT: Note that I haven't gotten into dagger SCOPES just yet, for now I don't care if this singleton presenter lives as long as my app. I'll fix that later.
It looks like you're recreating the Dagger component every time MainActivity.onCreate(Bundle) is called, and the activity is reinstantiated when you rotate the screen.
Like other scopes, #Singleton means there will be one instance of the object for the lifetime of the component, not for the lifetime of the JVM. You typically have to make sure there is only one instance of the #Singleton component yourself, usually by keeping it in a field in your Application.
You create a new dagger component every time here:
private void initializeDependencies() {
DaggerWeatherApiComponent.builder()
.build().inject(this);
}
A scoped dependency exists as ONE instance PER component.
If you create a new component, it will have its own scope, and it will create its own instance.
You should either invest in Mortar scopes to preserve your component, or you should have some sort of "cache" in your Application instance.