I cannot access the hi variable from my library class. Why? Check it out:
I have this inteface in my library:
interface ContextAccessor {
fun getApplicationContext(): Application?
}
And this code as well:
class SomeLibraryClass {
private var mContextAccessor: ContextAccessor?
String extractedHi = null
fun setContextAccessor(contextAccessor: ContextAccessor?) {
mContextAccessor = contextAccessor
}
fun someOtherMethod() {
mContextAccessor?.getAppContext()?.let { nonNullContext ->
// use nonNullContext here
extractedHi = nonNullContext.hi; // i get error here!
}
}
}
And this class in my project:
public class MyActivity extends Activity implements MyActivity.ContextAccessor {
private SomeLibraryClass someLibraryClassInstance = SomeLibraryClass();
public String hi = "hi";
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// ContextAccessor reference is set to some library class
someLibraryClassInstance.setContextAccessor(this);
}
#Override
protected void onDestroy() {
super.onDestroy();
// Super important!
someLibraryClassInstance.setContextAccessor(null);
// OR create some method like `someLibraryClassInstance.removeContextAccessor(this)`
}
#Override
public Application getApplicationContext() {
return super.getApplication();
}
}
Add hi property to the ContextAccessor interface:
interface ContextAccessor {
val hi: String
// ...
}
And in MyActivity implement getHi() method:
#NotNull
#Override
public String getHi() {
return hi;
}
In your library class SomeLibraryClass you can access to it like the following:
var extractedHi: String? = null
fun someOtherMethod() {
extractedHi = mContextAccessor?.hi
}
Related
I try to make sample login page with two fields (username, password) and save button with android architecture component, using android data binding, validating the data in viewmodel and from view model I make call to repository for remote server call as mentioned in official doc, remote server return me userid with success so how can I start new fragment from view model using this success? I learn something about singleLiveEvent and EventObserver, but I'm not able to find there clear usage example:
LoginViewModel
private MutableLiveData<String> snackbarStringSingleLiveEvent= new MutableLiveData<>();
#Inject
public LoginViewModel(#NonNull AppDatabase appDatabase,
#NonNull JobPortalApplication application,
#NonNull MyApiEndpointInterface myApiEndpointInterface) {
super(application);
loginRepository = new LoginRepository(application, appDatabase, myApiEndpointInterface);
snackbarStringSingleLiveEvent = loginRepository.getLogin(username.get(), password.get(), type.get());
}
public MutableLiveData<String> getSnackbarStringSingleLiveEvent() {
return snackbarStringSingleLiveEvent;
}
Repository
public SingleLiveEvent<String> getLogin(String name, String password, String type) {
SingleLiveEvent<String> mutableLiveData = new SingleLiveEvent<>();
apiEndpointInterface.getlogin(name, password, type).enqueue(new Callback<GenericResponse>() {
#Override
public void onResponse(Call<GenericResponse> call, Response<GenericResponse> response) {
mutableLiveData.setValue(response.body().getMessage());
}
#Override
public void onFailure(Call<GenericResponse> responseCall, Throwable t) {
mutableLiveData.setValue(Constant.FAILED);
}
});
return mutableLiveData;
}
Login Fragment
private void observeViewModel(final LoginViewModel viewModel) {
// Observe project data
viewModel.getSnackbarStringSingleLiveEvent().observe(this, new Observer<String>() {
#Override
public void onChanged(String s) {
}
});
}
How can I use EventObserver in above case? Any practical example?
Check out below example about how you can create single LiveEvent to observe only one time as LiveData :
Create a class called Event as below that will provide our data once and acts as child of LiveData wrapper :
public class Event<T> {
private boolean hasBeenHandled = false;
private T content;
public Event(T content) {
this.content = content;
}
public T getContentIfNotHandled() {
if (hasBeenHandled) {
return null;
} else {
hasBeenHandled = true;
return content;
}
}
public boolean isHandled() {
return hasBeenHandled;
}
}
Then declare this EventObserver class like below so that we don't end up placing condition for checking about Event handled every time, everywhere :
public class EventObserver<T> implements Observer<Event<T>> {
private OnEventChanged onEventChanged;
public EventObserver(OnEventChanged onEventChanged) {
this.onEventChanged = onEventChanged;
}
#Override
public void onChanged(#Nullable Event<T> tEvent) {
if (tEvent != null && tEvent.getContentIfNotHandled() != null && onEventChanged != null)
onEventChanged.onUnhandledContent(tEvent.getContentIfNotHandled());
}
interface OnEventChanged<T> {
void onUnhandledContent(T data);
}
}
And How you can implement it :
MutableLiveData<Event<String>> data = new MutableLiveData<>();
// And observe like below
data.observe(lifecycleOwner, new EventObserver<String>(data -> {
// your unhandled data would be here for one time.
}));
// And this is how you add data as event to LiveData
data.setValue(new Event(""));
Refer here for details.
Edit for O.P.:
Yes, data.setValue(new Event("")); is meant for repository when you've got response from API (Remember to return same LiveData type you've taken in VM instead of SingleLiveEvent class though).
So, let's say you've created LiveData in ViewModel like below :
private MutableLiveData<Event<String>> snackbarStringSingleLiveEvent= new MutableLiveData<>();
You provide value to this livedata as Single Event from repository like below :
#Override
public void onResponse(Call<GenericResponse> call, Response<GenericResponse> response) {
mutableLiveData.setValue(new Event(response.body().getMessage())); // we set it as Event wrapper class.
}
And observe it on UI (Fragment) like below :
viewModel.getSnackbarStringSingleLiveEvent().observe(this, new EventObserver<String>(data -> {
// your unhandled data would be here for one time.
}));
Event.java
public class Event<T> {
private T content;
private boolean hasBeenHandled = false;
public Event(T content) {
this.content = content;
}
/**
* Returns the content and prevents its use again.
*/
public T getContentIfNotHandled() {
if (hasBeenHandled) {
return null;
} else {
hasBeenHandled = true;
return content;
}
}
/**
* Returns the content, even if it's already been handled.
*/
public T peekContent() {
return content;
}
}
EventObserver.java
public class EventObserver<T> implements Observer<Event<? extends T>> {
public interface EventUnhandledContent<T> {
void onEventUnhandledContent(T t);
}
private EventUnhandledContent<T> content;
public EventObserver(EventUnhandledContent<T> content) {
this.content = content;
}
#Override
public void onChanged(Event<? extends T> event) {
if (event != null) {
T result = event.getContentIfNotHandled();
if (result != null && content != null) {
content.onEventUnhandledContent(result);
}
}
}
}
Example, In ViewModel Class
public class LoginViewModel extends BaseViewModel {
private MutableLiveData<Event<Boolean>> _isProgressEnabled = new MutableLiveData<>();
LiveData<Event<Boolean>> isProgressEnabled = _isProgressEnabled;
private AppService appService;
private SchedulerProvider schedulerProvider;
private SharedPreferences preferences;
#Inject
LoginViewModel(
AppService appService,
SchedulerProvider schedulerProvider,
SharedPreferences preferences
) {
this.appService = appService;
this.schedulerProvider = schedulerProvider;
this.preferences = preferences;
}
public void login(){
appService.login("username", "password")
.subscribeOn(schedulerProvider.executorIo())
.observeOn(schedulerProvider.ui())
.subscribe(_userLoginDetails::setValue,
_userLoginDetailsError::setValue,
() -> _isProgressEnabled.setValue(new Event<>(false)),
d -> _isProgressEnabled.setValue(new Event<>(true))
)
}
}
In Login Fragment,
viewModel.isProgressEnabled.observe(this, new EventObserver<>(hasEnabled -> {
if (hasEnabled) {
// showProgress
} else {
// hideProgress
}
}));
Using Event and EventObserver class we can achieve the same like SingleLiveEvent class but if you are thinking a lot of boilerplate code just avoid this method. I hope it would help you and give some idea about why we are using SingleEvent in LiveData.
I understand that Google gives the guidelines to use LiveData between the ViewModel and UI but there are edge cases where using LiveData as a SingleLiveEvent is like reinventing the wheel. For single time messaging between the view model and user interface we can use the delegate design pattern. When initializing the view model in the activity we just have to set the activity as the implementer of the interface. Then throughout our view model we can call the delegate method.
Interface
public interface Snackable:
void showSnackbarMessage(String message);
UI
public class MyActivity extends AppCompatActivity implements Snackable {
private MyViewModel myViewModel;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.my_layout);
this.myViewModel = ViewModelProviders.of(this).get(MyViewModel.class);
this.myViewModel.setListener(this);
}
#Override
public void showSnackbarMessage(String message) {
Toast.makeText(this, "message", Toast.LENGTH_LONG).show();
}
}
View Model
public class MyViewModel extends AndroidViewModel {
private Snackable listener;
public MyViewModel(#NonNull Application application) {
super(application);
}
public void setListener(MyActivity activity){
this.listener = activity;
}
private void sendSnackbarMessage(String message){
if(listener != null){
listener.showSnackbarMessage(message);
}
}
private void anyFunctionInTheViewModel(){
sendSnackbarMessage("Hey I've got a message for the UI!");
}
}
Been blocked on this for a number of days.
For some reason my the 'application' member field in AndroidModule.java is null after injection.
AndroidModule.java
#Module(
library = true
)
public class AndroidModule {
#Inject MittoApplication application;
#Provides #Singleton
SharedPreferences provideSharedPreferences() {
return PreferenceManager.getDefaultSharedPreferences( application );
}
}
ApplicationModule.java
#Module(
includes = { ApiModule.class, AndroidModule.class },
library = true
)
public class ApplicationModule {
private Application application;
public ApplicationModule( Application application ) {
this.application = application;
}
#Provides #Singleton
Application providesApplication() {
return application;
}
}
BaseActivity.java
public class BaseActivity extends AppCompatActivity {
private ObjectGraph objectGraph;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
objectGraph = MittoApplication.getApplication(this).buildScopedObjectGraph(getModules());
objectGraph.inject(this);
}
#Override
protected void onDestroy() {
objectGraph = null;
super.onDestroy();
}
protected List<Object> getModules( ) {
return Arrays.<Object>asList(
);
}
}
MittoApplication.java
public class MittoApplication extends Application {
private static Context context;
private ObjectGraph objectGraph;
public void onCreate() {
super.onCreate();
MittoApplication.context = getApplicationContext();
initializeObjectGraph();
}
public static MittoApplication getApplication( Context context ) {
MittoApplication mittoApplication = (MittoApplication) context.getApplicationContext();
return mittoApplication;
}
public ObjectGraph getObjectGraph() {
return this.objectGraph;
}
public ObjectGraph buildObjectGraph( List<Object> modules ) {
return ObjectGraph.create(modules.toArray() );
}
public ObjectGraph buildScopedObjectGraph( List<Object> modules ) {
return objectGraph.plus(modules.toArray() );
}
private ObjectGraph buildInitialObjectGraph( List<Object> modules ) {
return ObjectGraph.create( modules.toArray() );
}
private void initializeObjectGraph() {
objectGraph = buildInitialObjectGraph( Arrays.<Object>asList( new ApplicationModule(this) ));
}
public static Context getContext() {
return MittoApplication.context;
}
}
I've spend ages pondering over this, I've looked at countless examples and blog sites. Would love someone smarter then I to assist.
Thanks for reading.
Field injection works only with direct invocation of the object graph. To obtain the application instance for your provider method, you need to reference it as a parameter of the provider method.
//#Inject MittoApplication application; //this won't work
#Provides #Singleton
SharedPreferences provideSharedPreferences(Application application) { //this will
return PreferenceManager.getDefaultSharedPreferences( application );
}
Also, you never actually provided a MittoApplication, only an Application.
And it's worth noting that you are using Dagger1, so I believe you will need to change your AndroidModule to be complete=false:
#Module(
library = true,
complete = false
)
public class AndroidModule {
I want to call a method in MyCustomModuleClass extends ReactContextBaseJavaModule from another Class
MyCustomModule.java
public class MyCustomModule extends ReactContextBaseJavaModule {
private ReactContext mReactContext;
public MyCustomModule(ReactApplicationContext reactContext) {
super(reactContext);
mReactContext = reactContext;
}
#Override
public String getName() {
return "CustomModule";
}
private void sendEvent(String eventName, Object params) {
mReactContext
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit(eventName, params);
}
}
I'm trying this..
OtherClass.java
public class OtherClass extends AnotherClass {
#Override
protected void eventOccurred(Context context) {
MyCustomModule RNC = new MyCustomModule(?);//-> Can't call without ReactContext
RNC.sendEvent("CustomEvent", ObjectData);
}
}
Sorry, I know nothing about java, could any one please help me out?
Is there any way I can get ReactContext in OtherClass?
Here's how I did it in a FirebaseMessagingService extended class.
public class MyPushListener extends FirebaseMessagingService {
#Override
public void onMessageReceived(JSONObject message, JSONObject content){
MainApplication application = (MainApplication) this.getApplication();
ReactNativeHost reactNativeHost = application.getReactNativeHost();
ReactInstanceManager reactInstanceManager = reactNativeHost.getReactInstanceManager();
ReactContext reactContext = reactInstanceManager.getCurrentReactContext();
if (reactContext != null) {
WritableNativeArray params = new WritableNativeArray();
params.pushString(message.toString());
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit("EVENT_HAS_TRIGGERED", params);
}
}
}
Source
I am trying to understand how interfaces work. I have read basic interface tutorials online and watched a few videos so i do have a good idea of what a interface is and its advantages.
Interface
public interface UpyunFormApi {
#Description("上传文件")
#POST("/{bucket}")
#Multipart
public Request upload(#Path("bucket") String bucket, #Part("policy") PolicyPart policy,
#Part("signature") SignaturePart signature, #Part("file") File file, OnRequestListener requestListener,
OnResponseListener<UpyunFormEntity> responseListener, OnErrorListener errorListener);
Code
private UpyunFormApi formApi;
private void uploadAndPushTopic() {
String bucket = UrlManager.getInstance().getUpyunImageBucket();
String secret = UrlManager.getInstance().getUpyunImageSecret();
for (File file : filearr) {
PolicyPart policy = new PolicyPart(bucket);
SignaturePart signature = new SignaturePart(policy, secret);
formApi.upload(bucket, policy, signature, file, uploadRequestListener, uploadResponseListener,
uploadErrorListener);
}
}
private OnRequestListener uploadRequestListener = new OnRequestListener() {
#Override
public void onRequest(Request arg0) {
}
};
private OnErrorListener uploadErrorListener = new OnErrorListener() {
#Override
public void onError(LegolasException arg0) {
}
};
private OnResponseListener<UpyunFormEntity> uploadResponseListener = new OnResponseListener<UpyunFormEntity>() {
#Override
public void onResponse(UpyunFormEntity arg0) {
}
}
};
Why the Responselister works after "formApi.upload()" finished?And I can't find function definition.Help!
I don't understand the code
#Description("上传文件")
#POST("/{bucket}")
#Multipart
Make an interface like :
public interface ChangeItemInterface {
public void doChange(String anyValue);
}
In Adapter,
Intialize interface object like :
ChangeItemInterface changeItemInterface;
In Adapter Constructor,
this.changeItemInterface = context;
In Adapter, From any View Click :
AnyView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
this.changeItemInterface.doChange("AnyValue");
// It will go to the Your Activity Overided method which is explained below this
}
});
In Your Activity implement this interface like :
public class YourActivity extends Activity implements ChangeItemInterface{
/// You'll get override method of your interface, here your call back will come when from adapter click happen
#Override
public void doChange(String anyValue) {
/// Here you can update any value in your activity !
}
}
Hope this demo help you to understand interface use !
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();
}
}