I am trying to achieve MVVM design pattern in my application.I have created viewmodel and repository class but when I am trying to instantiate viewmodel in my MainActivity its showing error red line below MainActivity at the time of instantiation in below line.
pdfViewModel = new ViewModelProvider(MainActivity.this).get(PdfViewModel.class);
Below is my code:
MainActivity.java
public class MainActivity extends AppCompatActivity {
PdfViewModel pdfViewModel;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
pdfViewModel = new ViewModelProvider(MainActivity.this).get(PdfViewModel.class);
}
}
PdfViewModel.java
public class PdfViewModel extends AndroidViewModel {
private PdfRepository pdfRepository;
public PdfViewModel(#NonNull Application application) {
super(application);
pdfRepository = new PdfRepository(application);
}
public LiveData<List<Pdfs>> getAllPdfs(){
return pdfRepository.getMutableLiveData();
}
}
PdfRepository.java
public class PdfRepository {
private ArrayList<Pdfs> list = new ArrayList<>();
private MutableLiveData<List<Pdfs>> mutableLiveData = new MutableLiveData<>();
private Application application;
public PdfRepository(Application application){
this.application = application;
}
public MutableLiveData<List<Pdfs>> getMutableLiveData(){
SharedPreferences preferences = application.getSharedPreferences("Credentials", Context.MODE_PRIVATE);
String email = preferences.getString("email",null);
Retrofit retrofit = RetrofitClient.getInstance();
ApiService apiService = retrofit.create(ApiService.class);
Call<List<Pdfs>> call = apiService.getFiles(email);
call.enqueue(new Callback<List<Pdfs>>() {
#Override
public void onResponse(Call<List<Pdfs>> call, Response<List<Pdfs>> response) {
if(response.body() != null){
list = (ArrayList<Pdfs>) response.body();
mutableLiveData.setValue(list);
}
}
#Override
public void onFailure(Call<List<Pdfs>> call, Throwable t) {
TastyToast.makeText(application,t.getMessage(),TastyToast.LENGTH_SHORT,TastyToast.ERROR).show();
}
});
return mutableLiveData;
}
}
What needs to be corrected in the above code?
Your code is trying to create a new instance of the class ViewModelProvider (with the new keyword) and that's not the right way to instantiate a ViewModel.
On MainActivity, instead of:
pdfViewModel = new ViewModelProvider(MainActivity.this).get(PdfViewModel.class);
try:
pdfViewModel = ViewModelProviders.of(this).get(PdfViewModel.class);
Notice the right class is ViewModelProviders (with an "s" at the end) and you need to call the static method of instead of creating a new instance of it with new. If you can't import that class, make sure you have the dependency 'androidx.lifecycle:lifecycle-extensions:2.2.0' added to app/build.gradle.
To make your code even clearer, I'd suggest learning about the Kotlin KTX method viewModels, as described here. You'd need to use Kotlin for that though.
Related
I am implementing android pagination library in my app and would like to pass "id" of an item from my activity to the data source where my network call is made
AddCommentActivity.java
//I want to pass this string to the network call.
String image_id = getIntent().getStringExtra("image_id");
CommentViewModel commentViewModel = new ViewModelProvider(this).get(CommentViewModel.class);
CommentDataSource.java
public class CommentDataSource extends PageKeyedDataSource<Long, Comment> {
public CommentDataSource(){
progress_bar = new MutableLiveData<>();
}
#Override
public void loadInitial(#NonNull final LoadInitialParams<Long> params, #NonNull final LoadInitialCallback<Long, Comment> callback) {
RestApi restApi = RetrofitApi.create();
Call<CommentResponse> call = restApi.getComments(FIRST_PAGE, "I want the image_id from activity here");
call.enqueue(new Callback<CommentResponse>() {
#Override
public void onResponse(Call<CommentResponse> call, Response<CommentResponse> response) {
}
CommentDataSourceFactory.java
public class CommentDataFactory extends DataSource.Factory<Long, Comment> {
public MutableLiveData<CommentDataSource> commentLiveDataSource = new MutableLiveData<>();
public CommentDataFactory() {
}
#Override
public DataSource<Long, Comment> create() {
CommentDataSource commentDataSource = new CommentDataSource();
commentLiveDataSource.postValue(commentDataSource);
return commentDataSource;
}
CommentViewModel.java
public class CommentViewModel extends ViewModel {
public LiveData<PagedList<Comment>> commentPagedList;
public LiveData<CommentDataSource> liveDataSource;
public LiveData progressBar;
public CommentViewModel(){
CommentDataFactory commentDataFactory = new CommentDataFactory();
liveDataSource = commentDataFactory.commentLiveDataSource;
progressBar = Transformations.switchMap(liveDataSource, CommentDataSource::getProgressBar);
PagedList.Config config = new PagedList.Config.Builder()
.setEnablePlaceholders(false)
.setPageSize(CommentDataSource.PAGE_SIZE)
.build();
commentPagedList = new LivePagedListBuilder<>(commentDataFactory, config).build();
}
public LiveData<PagedList<Comment>> getCommentData(){
return commentPagedList;
}
public void getRefreshedData(){
getCommentData().getValue().getDataSource().invalidate();
}
}
How to do that.? I checked Passing variable to paging library class which is exactly what I want to do but I dont understand it and the code gives errors. Errors such as
Cannot create an instance of class CommentViewModel
CommentViewModel has no zero argument constructor
Okay do:
commentViewmodel1.getCommentData().observe(this, new Observer<PagedList<Comments>>(){
#Override
public void onChanged(PagedList<Comment>
comments){
adapter.submitList(comments);
}
});
Android newly here. I am working on a popular movies app that fetches some movie data from moved.org and display to users when tapping on movie thumbnail. Recently, I have been working on setting up retrofit in my app for two weeks. I am trying to use MVVM pattern. My goals is to get response from api.moviedb.org so that, eventually, I can display movie posters in recycler view. For some reason, I am not able to get any response from the api call using retrofit. I think I did something wrong with setting up retrofit but can't find out how I messed up... I feel like I am hard stuck.. Any help from experienced android developers would be much appreciated:)
Github link to my project
On create method in MainActivity:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mRecyclerView = findViewById(R.id.rvMovies);
movieViewModel = ViewModelProviders.of(this).get(MovieViewModel.class);
// Log.d("viewmodel", String.valueOf(movieViewModel));
movieViewModel.getMoviesLiveData().observe(this, new Observer<MovieResult>() {
#Override
public void onChanged(MovieResult movieResult) {
Log.d("movieResult", String.valueOf(movieResult));
}
});
}
ViewModel Class:
public class MovieViewModel extends AndroidViewModel {
private MutableLiveData<MovieResult> movieLiveData;
private MovieRepository movieRepository;
public MovieViewModel(#NonNull Application application){
super(application);
}
public void init(){
if (movieLiveData != null){
return;
}
movieRepository = MovieRepository.getInstance();
movieLiveData = movieRepository.getMovies("api_key_here");
}
public LiveData<MovieResult> getMoviesLiveData() {
init();
return movieLiveData;
}
}
Respository:
public class MovieRepository {
private static MovieRepository movieRepository;
public static MovieRepository getInstance(){
if (movieRepository == null){
movieRepository = new MovieRepository();
}
return movieRepository;
}
private GetMovieService service;
public MovieRepository(){
service = RetrofitInstance.getRetrofitInstance().create(GetMovieService.class);
}
public MutableLiveData<MovieResult> getMovies(String api){
Log.d("getmovies","called");
final MutableLiveData<MovieResult> movieData = new MutableLiveData<>();
service.getMovieResult(api).enqueue(new Callback<MovieResult>() {
#Override
public void onResponse(Call<MovieResult> call, Response<MovieResult> response) {
if (response.isSuccessful()){
movieData.setValue(response.body());
Log.d("debug", String.valueOf(movieData));
}
}
#Override
public void onFailure(Call<MovieResult> call, Throwable t) {
movieData.setValue(null);
Log.d("onfailure","why");
}
});
return movieData;
}
}
RetrofitInstance:
public class RetrofitInstance {
private static Retrofit retrofit;
private static final String BASE_URL = "http://api.themoviedb.org/3/";
public static Retrofit getRetrofitInstance(){
if(retrofit == null){
retrofit = new retrofit2.Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
}
return retrofit;
}
}
GetMovieService.Java
public interface GetMovieService {
#GET("movie/popular")
Call<MovieResult> getMovieResult(#Query("api_key") String apiKey);
}
logical:
log output
Hi I am trying to get an arraylist of data from a an async task class to another main class:
I was following the answer below but I am a little lost:
How to get the result of OnPostExecute() to main activity because AsyncTask is a separate class?
So I have my class that extends async task and calls to the database to get my object array:
public class GetVideoInfoFromDataBase extends AsyncTask {
// Paginated list of results for song database scan
static PaginatedScanList<AlarmDynamoMappingAdapter> results;
// The DynamoDB object mapper for accessing DynamoDB.
private final DynamoDBMapper mapper;
public interface AlarmsDataBaseAsyncResponse {
void processFinish(PaginatedScanList<AlarmDynamoMappingAdapter> output);
}
public AlarmsDataBaseAsyncResponse delegate = null;
public GetVideoInfoFromDataBase(AlarmsDataBaseAsyncResponse delegate){
mapper = AWSMobileClient.defaultMobileClient().getDynamoDBMapper();
this.delegate = delegate;
}
#Override
protected Object doInBackground(Object[] params) {
DynamoDBScanExpression scanExpression = new DynamoDBScanExpression();
results = mapper.scan(AlarmDynamoMappingAdapter.class, scanExpression);
return results;
}
#Override
public void onPostExecute(Object obj) {
delegate.processFinish(results);
}
}
There are no errors but I think I have done something incorrectly in it causing my error.
So in my main activity to call the results I have:
GetVideoInfoFromDataBase asyncTask =new GetVideoInfoFromDataBase(new GetVideoInfoFromDataBase.AlarmsDataBaseAsyncResponse(){
#Override
public void processFinish(PaginatedScanList<AlarmDynamoMappingAdapter> output) {
}
}).execute();
I have two problems here
I am getting the error:
"incompatible types: AsyncTask cannot be converted to GetVideoInfoFromDataBase"
In the mainactivity where i have:
`new GetVideoInfoFromDataBase(new GetVideoInfoFromDataBase.AlarmsDataBaseAsyncResponse()`
it wants me to cast it like this:
(GetVideoInfoFromDataBase) new GetVideoInfoFromDataBase(new GetVideoInfoFromDataBase.AlarmsDataBaseAsyncResponse()
That doesn't seem right but I thought i would check.
I am not sure how to return the result when overriding the onprocessfinished.
Thanks in advance for your help
First create an Interface
public interface AsyncInterface {
void response(String response);
}
Assign it in the asynctask class as below :-
Context context;
Private AsyncInterface asyncInterface;
AsyncClassConstructor(Context context){
this.context = context;
this.asyncInterface = (AsyncInterface) context;
}
Then inside onPostExecute method of asynctask class :-
#Override
protected void onPostExecute(String s) {
super.onPostExecute(s);
asyncInterface.response(s);
}
Then implement this interface in your activity :-
class MainActivity extends AppCompatActivity implements AsyncInterface {
and then import the method of asyncInterface
#Override
public void response(String response) {
//Here you get your response
Log.e(TAG, response);
}
Modify Constructor of class.
Need default constructor. By the way, create method to set Interface.
public void setInterface(AlarmsDataBaseAsyncResponse delegate){
this.delegate = delegate;}
In MainActivity, push your logic in:
object.setInterface(new AlarmsDataBaseAsyncResponse(){
#Override
public void processFinish(PaginatedScanList<AlarmDynamoMappingAdapter> output) {
//your logic
}
});
The question is in the subject, yet I'll repeat it again:
Is there a difference between the way Dagger2 treats #Singleton and custom sopes?
Also, if a class is annotated with some scope, is there a convenient way to expose it as a different scope (or unscoped), or do I need to write a provider method?
There is no difference between the way Dagger2 treats #Singleton and custom sopes.
Lets say we are using #User
#Scope
#Retention(RetentionPolicy.RUNTIME)
public #interface User {
}
#Module
public class TwitterModule {
private final String user;
public TwitterModule(String user) {
this.user = user;
}
#Provides
#User
Tweeter provideTweeter(TwitterApi twitterApi) {
return new Tweeter(twitterApi, user);
}
#Provides
#User
Timeline provideTimeline(TwitterApi twitterApi) {
return new Timeline(twitterApi, user);
}
}
#Module
public class NetworkModule {
#Provides
#Singleton
OkHttpClient provideOkHttpClient() {
return new OkHttpClient();
}
#Provides
#Singleton
TwitterApi provideTwitterApi(OkHttpClient okHttpClient) {
return new TwitterApi(okHttpClient);
}
}
#Singleton
#Component(modules = {NetworkModule.class})
public interface ApiComponent {
TwitterApi api();
TwitterComponent twitterComponent(TwitterModule twitterModule);
}
#User
#Subcomponent(modules = {TwitterModule.class})
public interface TwitterComponent {
TwitterApplication app();
}
public class MainActivity extends AppCompatActivity {
private static final String TAG = MainActivity.class.getSimpleName();
TwitterComponent twitterComponentForUserOne,twitterComponentForUserTwo;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ApiComponent apiComponent = DaggerApiComponent.create();
twitterComponentForUserOne = apiComponent.twitterComponent(new TwitterModule("Amit Shekhar"));
twitterComponentForUserTwo = apiComponent.twitterComponent(new TwitterModule("Sumit Shekhar"));
// use twitterComponentOne and twitterComponentTwo for two users independently
}
#Override
protected void onDestroy() {
super.onDestroy();
twitterComponentForUserOne = null;
twitterComponentForUserTwo = null;
}
}
Here just we have to make sure that when we do not need the twitterComponent for that user. We have to assign null so that it gets garbage collected as I am doing here in onDestroy();
Finally, everything depends on component,if you have an instance of component in Application class it is not going to be garbage collected for whole application life-cycle.
In my MainActivity I have a method called getAPI that returns an OTBServiceWrapper. This is used to setup retrofit for calling to an API.
In my MainActivityTest file I am trying to stub out the new OTBService().getService() call that the getApi method is making so I can return a MockedOTBService which changes the client to a custom one that return json.
As is, the current implementation will it the MockedOTBService if I had to place a logger within MockedOTBService but also falls through and calls the real api, which is not want I want in a test.
I am trying to stub the Retrofit API calls using Mockito and return json. I cant seem to understand why the stub is being called yet is not stubbing the method in question.
Notes:
I am using ActivityInstrumentationTestCase2
I am only running one test
If I add a verify(mockedOTBService, atLeastOnce()).getService(); is says it was never called.
If I change the when...thenReturn to use a mMainActivity = spy(getActivity()) there is not change and the real API is called.
Logcat Output
Logger﹕ MockedOTBService was called // Mock is called
Logger﹕ Real OTBService was called // Real API is called
Logger﹕ MainActivity getAPI method class is "$Proxy1" // Mock is shown in MainActivity
Logger﹕ RealAPIResponse JSON Parsed ID: 266 // Real API response returned
Real Flow
MainActivity.onCreate() > OTBService.getService() > OTBServiceWrapper.createSearch(...)
Trying to Achieve within Tests
MainActivity.onCreate() > MockedOTBService.getService() > OTBServiceWrapper.createSearch(...)
MainActivity.java
public class MainActivity extends Activity {
private OTBServiceWrapper serviceWrapper;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getApi().createSearch(...)
}
public OTBServiceWrapper getApi() {
return new OTBService().getService();
}
}
OTBService.java
public class OTBService {
public OTBServiceWrapper getService() {
RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint(Constants.API_URL)
.build();
return restAdapter.create(OTBServiceWrapper.class);
}
}
OTBServiceWrapper.java
public interface OTBServiceWrapper {
#POST(Constants.API_SEARCHES_POST_URL)
void createSearch(#Body Request request, Callback<Request.Response> callback);
}
MainActivityTest.java
public class MainActivityTest extends ActivityInstrumentationTestCase2<MainActivity> {
private OTBService mMockedOTBService;
private MainActivity mMainActivity;
private View mSearchButton;
public MainActivityTest() { super(MainActivity.class); }
#Override
protected void setUp() throws Exception {
super.setUp();
setActivityInitialTouchMode(true);
System.setProperty("dexmaker.dexcache", getInstrumentation().getTargetContext().getCacheDir().getPath());
mMockedOTBService = mock(OTBService.class);
when(mMockedOTBService.getService()).thenReturn(new MockedOTBService(getInstrumentation().getContext()).getService());
mMainActivity = getActivity();
mSearchButton = mMainActivity.findViewById(R.id.AbSearchButton);
mYourHolidayButton = mMainActivity.findViewById(R.id.AbYourHolidayButton);
}
public void testButtonActions() {
TouchUtils.clickView(this, mSearchButton);
...
}
}
MockedOTBService.java
public class MockedOTBService {
private Context context;
public MockedOTBService(Context context) { this.context = context; }
public OTBServiceWrapper getService() {
RestAdapter restAdapter;
restAdapter = new RestAdapter.Builder()
.setClient(new LocalJsonClient(context))
.setEndpoint(Constants.API_TEST_URL)
.build();
return restAdapter.create(OTBServiceWrapper.class);
}
}
LocalJsonClient.java
#SuppressLint("DefaultLocale")
public class LocalJsonClient implements Client { ... }
build.gradle
dependencies {
androidTestCompile 'com.google.dexmaker:dexmaker:1.0'
androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.0'
}
Remove the need for mocking your request by allowing the Activity to set the service.
In your MainActivity create a class variable and a class setter for the service. It needs to be a at the class scope to prevent the OnCreate method being called before you have set the service to what you want it to be. Also create an instance getter which sets the service if you have not already.
In your test before you call getActivity() set the service to be your mock service. (Maybe think about moving this out to a support object).
MainActivity.java
public class MainActivity extends Activity {
private static OTBServiceWrapper serviceWrapper;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getServiceWrapper.createSearch(...)
}
public OTBServiceWrapper getServiceWrapper() {
if (serviceWrapper == null) {
MainActivity.setServiceWrapper(new OTBService().getService());
}
return serviceWrapper;
}
public static void setServiceWrapper(OTBServiceWrapper serviceWrapper) {
MainActivity.serviceWrapper = serviceWrapper;
}
}
MainActivityTest.java
public class MainActivityTest extends ActivityInstrumentationTestCase2<MainActivity> {
private MainActivity mMainActivity;
public MainActivityTest() { super(MainActivity.class); }
#Override
protected void setUp() throws Exception {
super.setUp();
setActivityInitialTouchMode(true);
MainActivity.setServiceWrapper(
new MockedOTBService(getInstrumentation().getContext()).getService()
);
mMainActivity = getActivity();
}
}