I have some class application class extending Application. I use it as a controller for filling the database and perfoming REST calls. There is an interface:
private OnDBfilled onFilledListener;
...
public void setOnFilledListener(OnDBfilled onFilledListener) {
this.onFilledListener = onFilledListener;
}
public interface OnDBfilled {
void onFilledCallback();
}
...
When I've got required data call it to get known the Fragment, that data was successfully load from the internet and filled to database:
#Override
public void onResponse(Call call, Response response) throws IOException {
...//working with data
onFilledListener.onFilledCallback();
}
For the first time, when the call is done and database is filled I can see the result in the log:
private Application controller;
private Application.OnDBfilled listener;
...
controller = new Application();
listener = new Application.OnDBfilled() {
#Override
public void onFilledCallback() {
System.out.println("bd is filled. replacing fragments initiated editianal");
replaceFragment();
}
};
controller.setOnFilledListener(listener);
This works fine and I can see the logging message. But when I'm trying to do in the another fragment, I've got Null Pointer Exception in this line:
onFilledListener.onFilledCallback();
Here's code of another fragment, but it's almost equal:
private Application controller;
private Application.OnDBfilled filledDBListener;
...
//onCreateView
controller = new Application();
filledDBListener = new Application.OnDBfilled() {
#Override
public void onFilledCallback() {
swipeRefreshLayout.setRefreshing(false);
// fillEventListFromBD(UserData.getCity(getActivity()));
}
};
controller.setOnFilledListener(filledDBListener);
Any suggestions? Thanks!
OnDBfilled flid static
private static OnDBfilled onFilledListener;
or
fragment & viewpager use:
viewpager.setoffscreenpagelimit(9)
Related
I have a Xamarin.Android app with several activities and fragments. The app uses SignalR, connected with a .net core backend web app. There are several activities that may require visual modifications depending on the events called by the server. Is there any kind of in-app events that activities may subscribe to on creation that handles those required visual changes?
For example:
I am on an activity that shows 5 images related to a publication, and then the server sends a notification that the publication has been edited so the images have changed. In this case i would want that the SignalR client triggered some in-app event that updates the changed images on created activities of this kind.
I have came up with some kind of solution. I created a class called EventSubscriber that acts exactly how i wanted to.
public class EventSubscriber<T> : IEventSubscriber
{
#region Private members
private List<Tuple<T, Func<T, bool>>> Subscribtions { get; set; }
private ConcurrentDictionary<int, Func<T, bool>> WaitingObjectSubscribtions { get; set; }
#endregion
public EventSubscriber()
{
Subscribtions = new List<Tuple<T, Func<T, bool>>>();
WaitingObjectSubscribtions = new ConcurrentDictionary<int, Func<T, bool>>();
}
#region Subscribe
public void Subscribe(T adapter, Func<T, bool> function)
{
lock (Subscribtions)
{
Subscribtions.Add(new Tuple<T, Func<T, bool>>(adapter, function));
}
}
public int Subscribe(Func<T, bool> function)
{
lock (WaitingObjectSubscribtions)
{
int id = WaitingObjectSubscribtions.Count;
WaitingObjectSubscribtions.TryAdd(id, function);
return id;
}
}
#endregion
#region Unsubscribe
public void UnSubscribe(Tuple<T, Func<T, bool>> item)
{
lock (Subscribtions)
{
Subscribtions.Remove(item);
}
}
public void UnSubscribe(int id)
{
lock (WaitingObjectSubscribtions)
{
Func<T, bool> func;
WaitingObjectSubscribtions.TryRemove(id, out func);
}
}
public void UnSubscribeAll()
{
lock (Subscribtions)
{
Subscribtions.Clear();
}
WaitingObjectSubscribtions.Clear();
}
#endregion
#region Call Subscribed
public void CallSubscribed()
{
lock (Subscribtions)
{
foreach (var item in Subscribtions)
{
(var adapter, var function) = item;
if (!function(adapter))
{
Log.Debug("[EventSubscriber]", "Failed to notify adapter, will be automatically unsubscribed from this event");
UnSubscribe(item);
}
}
}
}
public void CallSubscribedWith(T adapter)
{
foreach (var id in WaitingObjectSubscribtions.Keys)
{
var function = WaitingObjectSubscribtions[id];
try
{
if (!function(adapter))
{
Log.Debug("[EventSubscriber]", "Failed to execute function");
UnSubscribe(id);
}
}
catch (Exception)
{
Log.Debug("[EventSubscriber]", "Failed to execute function");
UnSubscribe(id);
}
}
}
#endregion
}
To manage several EventSubscribed used in-app i created a static class Accessible to every Activity or Fragment which contains all the needed events:
public static class EventBoard
{
#region Products and Favorites
public static EventSubscriber<SwipeRefreshLayout> FinishedLoadingProducts = new EventSubscriber<SwipeRefreshLayout>();
public static EventSubscriber<List<Product>> SuccessfullyLoadedProducts = new EventSubscriber<List<Product>>();
public static EventSubscriber<RecyclerView.Adapter> UnsuccessfullyLoadedProducts = new EventSubscriber<RecyclerView.Adapter>();
//Search
public static EventSubscriber<SwipeRefreshLayout> FinishedLoadingProductsForSearch = new EventSubscriber<SwipeRefreshLayout>();
public static EventSubscriber<RecyclerView.Adapter> SuccessfullyLoadedProductsForSearch = new EventSubscriber<RecyclerView.Adapter>();
public static EventSubscriber<RecyclerView.Adapter> UnsuccessfullyLoadedProductsForSearch = new EventSubscriber<RecyclerView.Adapter>();
public static EventSubscriber<RecyclerView.Adapter> SearchNotFound = new EventSubscriber<RecyclerView.Adapter>();
//favorites
public static EventSubscriber<LocalProduct> ConfirmNewFavoriteEvent = new EventSubscriber<LocalProduct>();
public static EventSubscriber<LocalProduct> ConfirmRemoveFavoriteEvent = new EventSubscriber<LocalProduct>();
public static EventSubscriber<LocalProduct> NewFavoriteEvent = new EventSubscriber<LocalProduct>();
public static EventSubscriber<LocalProduct> RemoveFavoriteEvent = new EventSubscriber<LocalProduct>();
public static EventSubscriber<List<LocalProduct>> SetFavoritesEvent = new EventSubscriber<List<LocalProduct>>();
#endregion
}
So basically if a new activity is created it can subscribe to an EventSubscriber of its preference. Moreover, it can provide a function that receives any kind of object so it can be as flexible as it can.
The only thing that raise my concern, be sure you unsubscribe your events when the activity or the fragment View is destroyed because they can be a good source of memory leaks.
To communicate with the real-time server, you would have to use methods from the server's package. From researching, you can use the HubConnection.On method. Signalr registers the send and receive methods when you built the hub:
public class MyHub : Hub{
public async Task Send(string user, string message){
// Receive is the name of the listener method
await Clients.All.SendAsync("Receive", user, message);
}
}
Then, in your Android activities, you can register to the HubConnection (assuming you've sent the message about the edited image) of your listener method using the On method:
myHubConnection.On<string, string>("Receive", (user, message) =>
{
/* change UI here */
});
So I'm fairly new to MVVM. So I'm fetching my data in my VM and I'm passing in the Activity/fragment as a listener in the method call.
The reason I'm doing this is because I'm going to have a callback if there was to be an error. So I'd handle it in the activity/fragment with a dialog.
I'm not sure if I'm breaking MVVM here? If i'm making any other errors with this pattern, please let me know.
Thanks
In my view, fragment/activity
/*creating and using my VM inside my fragment*/
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState){
super.onActivityCreated(savedInstanceState);
//Create and observe data for any changes throughout the lifecycle
final OverviewViewModel viewModel = ViewModelProviders.of(this).get(OverviewViewModel.class);
//get info
viewModel.getUserInfo(this);
observeViewModel(viewModel);
}
//Listener in the activity/fragment that will handle an error in the request
#Override
public void onTokenExpired() {
ExpiredTokenDialogFragment dialogFragment = new ExpiredTokenDialogFragment();
dialogFragment.show(getFragmentManager(), EXPIRED_DIALOG);
}
My View model where i make request.
public void getUserInfo(AuthenticationListener listener){
mUserInformationObservable = mRepository.getUserInfo(listener);
}
My retrofit request
public LiveData<UserInformation> getUserInfo(final AuthenticationListener authenticationListener){
final MutableLiveData<UserInformation> data = new MutableLiveData<>();
mService.fetchFollowers().enqueue(new Callback<UserInformation>() {
#Override
public void onResponse(Call<UserInformation> call, retrofit2.Response<UserInformation> response) {
//note, the result is in data. Calling response.body.string twice results in an empty string
if(response.body()!=null) data.setValue(response.body());
}
#Override
public void onFailure(Call<UserInformation> call, Throwable t) {
if(t instanceof UnauthorizedException){
data.setValue(null);
mToken.setAccessToken(null);
authenticationListener.onTokenExpired();
}
}
});
return data;
}
Using a listener is not recommended. The android-architecture project uses a SingleLiveEvent class for events like navigation or displaying a Snackbar. You can use the same class for showing a dialog.
In your OverviewViewModel you can add another field:
final SingleLiveEvent<Void> tokenLiveData = SingleLiveEvent<Void>();
in your onFaliure callback you can use:
tokenLiveData.call()
instead of the callback.
In your activity subscribe to tokenLiveData and show a dialog when it emits a value.
I have a use case in which I need to update points(markers) on the map when a certain event occurs inside a service. For that am using an interface callback method.
public interface draw
{
void updatePoints(Context context) throws JSONException;
}
Now am using this interface in my Service like this :
draw dfc;
...............
//Inside a constructor of a class
dfc=new draw() {
#Override
public void updateFloatingPoints(Context context) throws JSONException {
System.out.println("Callback in Service");
CustomViewView.registerDPCListener(dfc);
}
};
Now within the same Service, under a certain function, am passing the interface as an input parameter i.e callback and inside that function am calling
callback.updateFloatingPoints(context);
Now when am running the service, I can see the callback in Service statement. Till here everything is okay.
Now problem is that, when the callback is received, I want to do some work another class. Let's call it CustomView. I implemented the interface in that class too but I am not able to receive the callbacks over there. Follow is the bare bone code of the class
public class CustomView extends ABCD implements draw {
private draw dfcListner=null;
public void registerDPCListener(drawPath listener) throws JSONException {
this.dfcListner = listener;
if(dfcListner!=null)
{
System.out.println("Listener initialized");
//dfcListner.updateFloatingPoints(mcontext);
}
else
{
System.out.println("Listener is not initialized");
}
}
//Another functions in between over here
#Override
public void updateFloatingPoints(Context context) throws JSONException {
System.out.println("Inside CustomView callback");
}
}
How do I go about it?
I understand there are a lot of information about it out there, but I haven't found one that matches my case yet.
I have a recycleview on a fragment that is always open, so the fragment basically never re-creates itself.
This is my code to load the adapter.
reLoad(); //method shown below
mRecycler.setAdapter(new SolicitationAdapter(myRealm.where(SolicitationDatabase.class).findAllAsync()));
And this is the logic I came up with:
public void reLoad() {
if (!myRealm.where(SolicitationDatabase.class).findAll().isEmpty()) {
mNothingHere.setVisibility(View.GONE);
mRecycler.setVisibility(View.VISIBLE);
} else {
mNothingHere.setVisibility(View.VISIBLE);
mRecycler.setVisibility(View.GONE);
}
}
It works great the first time the user opens the app.
The trouble starts when the user creates a record, since the fragment doesn't re-create itself it never reloads.
The reason I haven't been able to reload after user adds something is because the method to add a new record is on a singleton being called from a different activity. Which means when I try to do it I get a nullpointerexception when declaring the the recycleview and the textview.
Edit - What I tried (reloading views from another place)
I have a class called PostHelper, this class is in charge of posting a new record.
This is the constructor:
public PostHelper(Context context, Activity activity) {
this.mContext = context;
this.mActivity = activity; //I call this in order to use "findViewById"
This is where the post happens:
public String addSolicitation(File _file, boolean fromQueue) {
//loading view
TextView nothingHere = (TextView) mActivity.findViewById(R.id.nothing_here);
RecyclerView recycler = (RecyclerView) mActivity.findViewById(R.id.recycler);
...so on until after the post:
SolicitationAdapter n = new SolicitationAdapter(myRealm.where(SolicitationDatabase.class).findAll());
n.notifyDataSetChanged();
nothingHere.setVisibility(View.GONE);
recycler.setVisibility(View.VISIBLE);
And this is the stacktrace:
06-01 21:43:37.511 9122-9122/? E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.ga.realm3, PID: 9122
io.reactivex.exceptions.OnErrorNotImplementedException: Attempt to invoke virtual method 'void android.widget.TextView.setVisibility(int)' on a null object reference
Edit 2 - I load PostHelper class using the following:
mPostHelper = new PostHelper(this, PostSolicitationActivity.this);
You're supposed to make sure that SolicitationAdapter is a RealmRecyclerViewAdapter, like so:
public class SolicitationAdapter extends RealmRecyclerViewAdapter<SolicitationDatabase, SolicitationViewHolder> {
public SolicitationAdapter(OrderedRealmCollection<SolicitationDatabase> results) {
super(results, true);
}
...
}
And then what you need to do is that you put the RealmResults as a field reference in your Activity:
public class PostSoliticiationActivity extends AppCompatActivity {
RealmResults<Solicitation> results;
Realm realm;
RealmChangeListener<RealmResults<Solicitiation> realmChangeListener = (results) -> {
if(results.isLoaded() && results.isValid()) {
if(results.isEmpty()) {
mNothingHere.setVisibility(View.GONE);
mRecycler.setVisibility(View.VISIBLE);
} else {
mNothingHere.setVisibility(View.VISIBLE);
mRecycler.setVisibility(View.GONE);
}
}
}
SolicitationAdapter adapter;
#Override
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
setContentView(R.layout.soliticiation_activity);
// bind views
realm = Realm.getDefaultInstance();
results = realm.where(SolicitationDatabase.class).findAllSortedAsync("id");
// .sort("id").findAllAsync(); in 4.3.0+
results.addChangeListener(realmChangeListener);
adapter = new SoliticiationAdapter(results);
mRecycler.setAdapter(adapter);
// layout manager as well
}
#Override
public void onDestroy() {
results.removeChangeListener(realmChangeListener);
realm.close();
super.onDestroy();
}
}
So things you don't need:
1.) reLoad() method
2.) onPostAdded callback
3.) PostActionListener
As long as you just add the SoliticiationDatabase to the Realm in a transaction, it'll all work without manually syncing ui.
If I understand correctly, you want to be notified in another view when an action happens elsewhere.
The way to do that is usually interfaces.
public class PostHelper {
// Define these
public interface PostActionListener {
void onPostAdded();
}
private PostActionListener postListener;
public void setPostActionListener(PostActionListener listener) throws ClassCastException {
this.postListener = (PostActionListener) context;
}
public PostHelper(Context context) {
this.mContext = context;
// Cast the passed context as a listener
if (mContext instanceof PostActionListener) {
this.postListener = (PostActionListener) mContext;
}
}
public String addSolicitation(File _file, boolean fromQueue) {
// Do something
// Callback to the UI to update
if (this.postListener != null) {
this.postListener.onPostAdded();
}
}
Then, in your initial Activity
public class YourActivity extends AppCompatActivity
implements PostHandler.PostActionListener {
// ... fields
#Override
public void onPostAdded() {
reLoad();
}
public void reLoad() {
boolean emptyList = myRealm.where(SolicitationDatabase.class).findAll().isEmpty();
mNothingHere.setVisibility(emptyList ? View.VISIBLE : View.GONE);
mRecycler.setVisibility(emptyList ? View.GONE : View.VISIBLE);
}
#Override
public void onCreate(Bundle b) {
...
mPostHelper = new PostHelper(this);
}
However, since you are using Realm, and there really is no data that you need to "return" here, then you can simply let the Android Activity lifecycle refresh the data for you.
I need to mock some static methods, that's fine so far and can be done like this:
#RunWith(PowerMockRunner.class)
#PrepareForTest({DataService.class})
public class PlayersAllViewModelTest {
// mock objects
private PlayersAllContextHandler mContextHandler;
private PlayersAllAdapter mAdapter;
#Before
public void setUp() throws Exception {
mockStatic(DataService.class);
//define mocks
mContextHandler = mock(PlayersAllContextHandler.class);
mAdapter = mock(PlayersAllAdapter.class);
}
#Test
public void check_init_requests_are_done() throws Exception {
// create instance of viewmodel
new PlayersAllViewModel(mContextHandler, mAdapter);
// check dataservice is requested for method 'getAllPlayers()'
PowerMockito.verifyStatic();
DataService.getAllPlayers(any(DataServiceCallback.class));
}
I need to test the behavior for a given response (success() / failure()) answered in a callback. The normal way to do so is like this:
// define mock answer
doAnswer(new Answer<MyCallback<String>>() {
#Override
public MyCallback answer(InvocationOnMock invocation) throws Throwable {
MyCallback<Player> callback = (MyCallback<Player>) invocation.getArguments()[0];
callback.onFailure(new UnsupportedOperationException());
return null;
}
}).when(>>mocked class instance<<).myTestMethod(any(MyCallback.class));
Because is want to call a static method, i can't do it like that so. There's no mocked instance of a class that could fill the gap :(
Does anybody know what's the correct way to do it?