I'm just getting into Android Development using Xamarin with Visual Studio 2015 Update 3. I'm trying to create a simple spinner, but something is going very, very wrong with the order of execution in the debugger.
In shared code, I have an options class that contains common values for dropdowns, etc.
public interface IOptionsCache : INotifyPropertyChanged
{
// is not observable collection on purpose, the entire list is replaced
// when data is fetched from the server
IList<string> States {get;}
}
Also in shared code, I have a standard base type to implement INotifyPropertyChanged more easily.
public abstract class NotifyDtoBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected bool SetField<T>(ref T field, T value, IEqualityComparer<T> comparer = null, [CallerMemberName] string propertyName = null)
{
comparer = comparer ?? EqualityComparer<T>.Default;
if (comparer.Equals(field, value))
{
return false;
}
field = value;
OnPropertyChanged(propertyName);
return true;
}
}
Then the actual OptionsCache looks like
public class OptionsCache : NotifyDtoBase, IOptionsCache
{
protected IList<string> _states;
public IList<string> States
{
get { return this._states; }
set { SetField(ref this._states, value); }
}
public async Task PopulateCacheAsync()
{
// TODO: fetch options from server
// for now, populate inline
this.States = new List<string>(){ "MI", "FL", "ME", ... }
}
}
These are working just fine. So, in my Activity, I new up an options class, and try and populate a drop down, but things go very wrong.
[Activity (Label = "Simple App", MainLauncher = true, Icon = "#drawable/icon")]
public class MainActivity : Activity
{
protected IOptionsCache OptionsCache;
protected override void OnCreate (Bundle bundle)
{
base.OnCreate (bundle);
// Set our view from the "main" layout resource
SetContentView (Resource.Layout.Main);
// new OptionsCache has no options to start with
// need to call PopulateCache to get data
var options = new OptionsCache();
// When the list is populated, populate the spinners
options.PropertyChanged += (sender, args) => PopulateSpinners();
// HERE IS WHERE THINGS TO BAD!
// As I step thru the code, when I hit this line (pre-execution) and then F10 to step over, it drops into the PopulateSpinners method and this.OptionsCache == null
this.OptionsCache = options;
// populate the cache async and let the property changed event
// populate the spinners
options.PopulateCacheAsync();
}
protected void PopulateSpinners()
{
Spinner statesSpinner = FindViewById<Spinner>(Resource.Id.StatesSpinner);
// this.OptionsCache == null
ArrayAdapter<string> departureAdapter = new ArrayAdapter<string>(this, global::Android.Resource.Layout.SimpleSpinnerDropDownItem, this.OptionsCache.States.ToArray());
statesSpinner.Adapter = departureAdapter;
}
}
It seems like my assignment call to the class variable is being skipped and the next method is being invoked. Not sure if it has to do with being an async method or what's going on here...
Well, the good news is that the code does actually run correctly... when it is actually deployed to the emulator.
It would seem that the latest code does not always deploy to the emulator and you may end up debugging 'old' code. One way to tell this is happening, is that the debugger will indicate invalid lines as the next statement (eg, it may say that a blank line or a class definition is the next statement, which is obviously not correct).
https://forums.xamarin.com/discussion/45327/newest-version-of-code-not-always-deployed-when-debugging-from-xamarin-studio
Related
I am trying to add an observer in my activity but it never seems to get triggered.
I have a button on my app which makes a sensor connected to my phone start measuring data when the sensors are measuring it hits a callback in my XsDevice() class.
Here is the code in my XsDevice() class
private MutableLiveData<ArrayList<Float>> accelerationData = new MutableLiveData<>();
public LiveData<ArrayList<Float>> freeAccDataLiveData = accelerationData;
#Override
public void onXsensDotDataChanged(String s, XsensDotData xsensDotData) {
ArrayList<Float> result = new ArrayList();
for (Float freeAcc: xsensDotData.getFreeAcc()) {
result.add(freeAcc);
}
accelerationData.postValue(result);
}
When the callback function is hit I am using postValue(result) to update the accelerationData variable, this is where me being a new to android development comes in.
I am presuming after I post the value the freeAccDataLiveData variable is updated and this is what I am observing.
Here is my observer code in my activity
private XsDevice xsDeviceClass = new XsDevice();
protected void onCreate(Bundle savedInstanceState) {
...
xsDeviceClass.freeAccDataLiveData.observe(this, new Observer<ArrayList<Float>>() {
#Override
public void onChanged(ArrayList<Float> freeAccData) {
for(int i = 0; i < freeAccData.size(); i++){
Log.d("Free Acceleration Data", String.valueOf(freeAccData.get(i)));
}
}
});
}
The ... is just a placeholder for the standard onCreate code I haven't included.
The issue I am having is Log.d("Free Acceleration Data", String.valueOf(freeAccData.get(i))); is never logged which must mean the observer isn't working. If I added this log directly to the callback function it works fine but I need to get the data in my MainActivity
Is there something simple I might have missed?
The observer of livedata will be called only when data is changed by set or post. Thus, if you did not initialize the value in the XsDevice class, it won't be called until data is assigned.
If you want to get callback in OnCreate method by default, need to set the default value of accelerationData like this. Then, you can get callback right after you register an observer.
class XsDevice {
private MutableLiveData<ArrayList<Float>> accelerationData = new MutableLiveData<>();
public LiveData<ArrayList<Float>> freeAccDataLiveData = accelerationData;
public XsDevice() {
accelerationData.postValue(new ArrayList<>());
}
...
}
Also, please make sure onXsensDotDataChanged is called as you expected and freeAccData is not empty. Otherwise, you cannot see the log even though it is called.
Would like to have your help on my weird problem that currently I am facing. I tried for couple of days but no luck and finally decided to post here to take help.
I created a Snapshot Listener attached to a Collection in Firebase defined as follows :-
public class FirebaseTypingStatusLiveData extends LiveData<List<documentSnapshot>> {
// Logging constant
private static final String TAG = "FirebaseQueryLiveData";
// Document Reference
private final DocumentReference documentReference;
// Listener
private final MyDocumentListener listener = new MyDocumentListener();
// Handler
private final Handler handler = new Handler();
private ListenerRegistration listenerRegistration;
// Flag to remove listener
private boolean listenerRemovePending = false;
private MutableLiveData <List<documentSnapshot> mutableLiveData = new MutableLiveData<>();
// Constructor
public FirebaseTypingStatusLiveData(DocumentReference documentReference) {
this.documentReference = documentReference;
}
public LiveData<List<documentSnapshot>> checknow(){
// Add listener
if (!Listeners.LIVESAMPLE.containsKey(documentReference)) {
listenerRegistration = documentReference.addSnapshotListener(listener);
Listeners.LIVESAMPLE.put(documentReference, listenerRegistration);
} else {
listenerRegistration = Listeners.LIVETYPINGSTATUSSAMPLE.get(documentReference);
}
return mutableLiveData;
}
// Listener definition
private class MyDocumentListener implements EventListener<DocumentSnapshot> {
#Override
public void onEvent(#Nullable DocumentSnapshot documentSnapshot, #Nullable
FirebaseFirestoreException e) {
Log.d(TAG, "onEvent");
// Check for error
if (e != null) {
// Log
Log.d(TAG, "Can't listen to query snapshots: " + documentSnapshot
+ ":::" + e.getMessage());
return;
}
setValue(documentSnapshot);
mutableLiveData.setValue(documentSnapshot);
}
}
}
}
The snapshot reads the data perfectly and advised as and when data is available.
The snapshot data is getting displayed 1. in Fragment (not part of Activity that i am talking about) 2. Activity through two view models that have the same code as follows :
#NonNull
public LiveData<List<documentSnapshot>> getDataSnapshotLiveData() {
Firestore_dB db = new Firestore_dB();
DocumentReference docref = db.get_document_firestore("Sample/"+docID);
FirebaseTypingStatusLiveData firebaseTypingStatusLiveData = new
FirebaseTypingStatusLiveData(docref);
return firebaseTypingStatusLiveData.checknow();
}
The Fragment & Activity code is also same except changing owner which are as follows :-
LiveData<List<documentSnapshot>> liveData = viewmodel.getDataSnapshotLiveData();
liveData.observe(this, new Observer<List<documentSnapshot>>() {
#Override
public void onChanged(DocumentReference docreef) {
String name = docreef.get("name");
stringname.setText(name); // The text is displaying either in Fragment or in Activity but not in both.
});
My problem is i need data in both i.e. Fragment & Activity whereas I am getting data either in Fragment or in Activity depending upon the code which I commented.
Kindly advise where I am making mistake. Thanks in Advance
Honestly, I am not sure that my answer wouldn't lead you away to the false way, but you can try.
My guess is that your problem could be somehow connected with ViewModel sharing.
There is a well-known task How to share Viewmodel between fragments.
But in your case, that can't help, because you have to share ViewModel between activities (now you have two separate ViewModels and that could be problem with Firestore EventListeners).
Technically you can share ViewModel between activities (I haven't try since usually I use Single activity pattern). For that as a owner parameter in ViewModelProvider constructor you can set instance of your custom Application class (but you have implement interface ViewModelStoreOwner for it). After that both in your activity and in your fragment you can get the same ViewModel with the Application class-instance:
val sharedViewModel = ViewModelProvider(mainApplication, viewModelFactory).get(SharedViewModel::class.java)
I made LiveData static that listens to changes in source data and provide updated content were ever required in different Activity.
I'm implementing the MVP design pattern. My presenter receives the new values from the view. I want to manage the state of a next button by automatically check if everything is valid when values are updated on the view.
In my form I have an optional part which is displayed only if the user select the correct option.
In this optional part I have a binary question. If the part is not displayed I need to set the value of the question to null on the Presenter side.
For example, the user select the option and the optional part is displayed. The user select the answer. Then the user change the option and the optional part is hidden. In that case I need to set the answer to the optional question to null, for the answer to not be already selected if the user display the optional part again.
To do so, I call a method on the Presenter with a null value instead of true/false.
Here is the code:
private final PublishSubject<Boolean> mObsOptionalAnswer = PublishSubject.create();
public MyPresenter(){
// Combine all the values together to enable/disable the next button
Observable.combineLatest(
// ... other fields
// I need this to return false if the optional part is
// displayed but nothing is selected
mObsOptionalAnswer.map(this::isValid),
(...) -> ...
).subscrible(enable ->{
mView.enableBtn(enable);
});
}
public void myFunction(Boolean isSomething){
// ... some code
mObsOptionalAnswer.onNext(isSomething);
}
private boolean isValid(Boolean value){
return value != null;
}
The problem is, since RxJava 2, null values are not allowed in the onNext() method.
So, how am I supposed to manage that?
If you want to be able to send a null value, you can use a wrapper. In this configuration, you send the wrapper, which isn't null even if the value itself is.
public class BooleanWrapper {
public final Boolean value;
public BooleanWrapper(Boolean value) {
this.value = value;
}
}
Your PublishSubject<Boolean> becomes a PublishSubject<BooleanWrapper> and you just have to create the wrapper and de-reference your Boolean when needed :
mObsOptionalAnswer.onNext(new BooleanWrapper(isSomething));
and
mObsOptionalAnswer.map(wrapper -> this.isValid(wrapper.value))
If you need to do that more than once in your code, you can create a generic wrapper (as described by this tutorial) :
public class Optional<M> {
private final M optional;
public Optional(#Nullable M optional) {
this.optional = optional;
}
public boolean isEmpty() {
return this.optional == null;
}
public M get() {
return optional;
}
}
you could use a constante Boolean object
public static final Boolean RESET_VALUE = new Boolean(false);
and you can emit this instead of emitting null. The receiver would have to check against this instance and behaving accordingly. Eg.
.subscrible(enable ->{
if (enable != RESET_VALUE) {
mView.enableBtn(enable);
}
});
I am using a singleton for fetching data from a web service and storing the resulting data object in an ArrayList. It looks like this:
public class DataHelper {
private static DataHelper instance = null;
private List<CustomClass> data = null;
protected DataHelper() {
data = new ArrayList<>();
}
public synchronized static DataHelper getInstance() {
if(instance == null) {
instance = new DataHelper();
}
return instance;
}
public void fetchData(){
BackendlessDataQuery query = new BackendlessDataQuery();
QueryOptions options = new QueryOptions();
options.setSortBy(Arrays.asList("street"));
query.setQueryOptions(options);
CustomClass.findAsync(query, new AsyncCallback<BackendlessCollection<CustomClass>>() {
#Override
public void handleResponse(BackendlessCollection<CustomClass> response) {
int size = response.getCurrentPage().size();
if (size > 0) {
addData(response.getData());
response.nextPage(this);
} else {
EventBus.getDefault().post(new FetchedDataEvent(data));
}
}
#Override
public void handleFault(BackendlessFault fault) {
EventBus.getDefault().post(new BackendlessFaultEvent(fault));
}
});
}
public List<CustomClass> getData(){
return this.data;
}
public void setData(List<CustomClass> data){
this.data = data;
}
public void addData(List<Poster> data){
this.data.addAll(data);
}
public List<CustomClass> getData(FilterEnum filter){
if(filter == FilterEnum.NOFILTER){
return getData();
}else{
// Filtering and returning filtered data
}
return getData();
}
}
The data is fetched correctly and the list actually contains data after it. Also, only one instance is created, as intended. However, whenever I call getData later, the length of this.data is 0. Because of this I also tried it with a subclass of Application holding the DataHelper object, resulting in the same problem.
Is there a good way of debugging this? Is there something like global watches in Android Studio?
Is there something wrong with my approach? Is there a better approach? I am mainly an iOS developer, so Android is pretty new to me. I am showing the data from the ArrayList in different views, thus I want to have it present in an the ArrayList as long as the application runs.
Thanks!
EDIT: Example use in a list view fragment (only relevant parts):
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
filter = FilterEnum.NOFILTER;
data = DataHelper.getInstance().getData(filter);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
customClassListAdapter = new customClassListAdapter(getActivity(), data);}
EDIT2: Added code where I fetch the data from Backendless, changed reference of DataHelper to reference of data in first EDIT
EDIT3: I usa a local EventBus for notifying the list view about the new data. This looks like this and works (initially the data gets populated, but after e.g. applying a filter, the ArrayList I get with getData is empty):
#Subscribe
public void onMessageEvent(FetchedDataEvent event) {
customClassListAdapter.notifyDataSetChanged();
}
Try instead of keeping reference to your DataHelper instance, keeping reference to your list of retrieved items. F.e. when you first fetch the list (and it's ok as you say), assign it to a class member. Or itarate through it and create your own array list of objects for future use.
Okay I finally found the problem. It was not about the object or memory management at all. Since I give the reference on getData to my ArrayAdapter, whenever I call clear (which I do when changing the filter) on the ArrayAdapter, it empties the reference. I basically had to create a copy of the result for the ArrayAdapter:
data = new ArrayList<>(DataHelper.getInstance().getData(filter));
I was not aware of the fact that this is a reference at all. So with this the data always stays in the helper entirely. I only did this because this:
customClassListAdapter.notifyDataSetChanged();
does hot help here, it does not call getData with the new filter again.
Thanks everyone for your contributions, you definitely helped me to debug this.
It is likely that getData does get called before the data is filled.
A simple way to debug this is to add (import android.util.Log) Log.i("MyApp.MyClass.MyMethod", "I am here now"); entries to strategic places in fetchData, addData and getData and then, from the logs displayed by adb logcat ensure the data is filled before getData gets called.
I'm writing tests for a simple Android application (it's a school project) and I'm having trouble testing the activity ContactListActivity which extends Android's ListActivity.
What I would like to test
Clicking the first item in ContactListActivity's ListView and checking if the ContactDetailActivity was started.
Problem
The list data comes from an SQLite database. For testing, I'm loading test data into the ListView, so the test won't be working with live data. Loading the test data works fine. Watching the emulator while running the test, I can see the activity being started and the test data appearing in the list. However, trying to access the first (and only) list item fails.
Test method
#UiThreadTest
public final void testLoadContactDetail() {
ListView list = activity.getListView();
assertNotNull(list);
ContactsListAdapter adapter = new ContactsListAdapter(
getInstrumentation().getContext(),
createData() // Creates an ArrayList of test data
);
list.setAdapter(adapter);
adapter.notifyDataSetChanged();
// list.getAdapter().getCount() is expectedly 1
// list.getChildCount() is unexpectedly 0
assertNotNull(list.getChildAt(0)); // Assertion fails
// (...)
}
As can be seen, I'm annotating the test with #UIThreadTest to be able to manipulate view elements. A new ListAdapter is created with test data and set to the list. Then, adapter.notifyDataSetChanged() makes sure that the list knows about the new data.
Question
How can I load test data from within an ActivityInstrumentationTestCase2 into a ListView so that the data will not only be displayed on screen, but actually "be there", meaning the list item can be fetched with list.getChildAt(0) and be clicked?
Entire test case
public class ContactListActivityFunctionalTest extends
ActivityInstrumentationTestCase2<ContactListActivity> {
private ContactListActivity activity;
public ContactListActivityFunctionalTest() {
super(ContactListActivity.class);
}
protected void setUp() throws Exception {
super.setUp();
setActivityInitialTouchMode(false);
activity = getActivity();
}
protected void tearDown() throws Exception {
super.tearDown();
}
#UiThreadTest
public final void testLoadContactDetail() {
ListView list = activity.getListView();
assertNotNull(list);
ContactsListAdapter adapter = new ContactsListAdapter(
getInstrumentation().getContext(),
createData()
);
list.setAdapter(adapter);
adapter.notifyDataSetChanged();
assertNotNull(list.getChildAt(0));
// Anything beyond this point is never executed,
// because the above assertion fails, and I have no idea
// if this test code is correct at all.
ActivityMonitor monitor = getInstrumentation().addMonitor(
ContactDetailActivity.class.getName(), null, false
);
TouchUtils.clickView(this, list.getChildAt(0));
ContactDetailActivity contactDetailActivity =
(ContactDetailActivity)monitor.waitForActivityWithTimeout(2000);
assertNotNull(contactDetailActivity);
assertTrue(getInstrumentation().checkMonitorHit(monitor, 1));
contactDetailActivity.finish();
}
private List<ContactInterface> createData() {
ContactInterface contact = new Contact();
contact.setId(1L);
contact.setName("Unit Test").setPhone("0123456789").setPosition(3);
List<ContactInterface> contacts = new ArrayList<ContactInterface>();
contacts.add(contact);
return contacts;
}
}
It looks like the listView.getChildAt method returns visible views. https://stackoverflow.com/a/6767006/693752
So, my guess is that the item is not visible yet. None are as getChildCount is returning 0. Maybe you should either :
wait a bit before asserting. Ok, it's dirty but UI testing needs it sometime.
post the assert inside a runnable on the ui thread so that it gets executed after the listview is executed. This will turn your test into something a bit more complex as you would have to synchronize the future runnable and the current testing thread a countDownLatch. And for this, you should consider not using #UIThreadTest.
I know I've asked how to load test data from within an ActivityInstrumentationTestCase2, but perhaps the answer to the question is to use ActivityUnitTestCase rather than ActivityInstrumentationTestCase2 in this particular case:
General activity behaviour is being tested, rather than interaction with other components
Well, it works...
Here is the rewritten, working test case that tests whether the ListView exists and whether the correct activity is started after a click on the list's first item.
public class ContactListActivityTest
extends ActivityUnitTestCase<ContactListActivity> {
private ContactListActivity activity;
public ContactListActivityTest() {
super(ContactListActivity.class);
}
protected void setUp() throws Exception {
super.setUp();
Intent intent = new Intent(
getInstrumentation().getTargetContext(), ContactListActivity.class
);
startActivity(intent, null, null);
activity = getActivity();
}
protected void tearDown() throws Exception {
super.tearDown();
}
public final void testItemClick() {
getInstrumentation().callActivityOnStart(activity);
getInstrumentation().callActivityOnResume(activity);
// Check if list exists
ListView list = activity.getListView();
assertNotNull("Intent was null", list);
// Load test data
ContactsListAdapter adapter = new ContactsListAdapter(
getInstrumentation().getContext(),
createData()
);
list.setAdapter(adapter);
adapter.notifyDataSetChanged();
assertEquals(2, adapter.getCount());
// Check if list has at least one item to click
View firstItem = list.getAdapter().getView(0, null, null);
assertNotNull(firstItem);
// Perform a click on the first item
list.performItemClick(
firstItem,
0,
list.getAdapter().getItemId(0)
);
// Check if the contact details activity got started
Intent intent = getStartedActivityIntent();
assertNotNull(intent);
assertEquals(
ContactDetailActivity.class.getName(),
intent.getComponent().getClassName()
);
}
private List<ContactInterface> createData() {
List<ContactInterface> contacts = new ArrayList<ContactInterface>();
ContactInterface contact = new Contact();
contact.setId(1L);
contact.setName("Jane Doe").setPhone("0123456789").setPosition(1);
contacts.add(contact);
ContactInterface contact2 = new Contact();
contact2.setId(2L);
contact2.setName("John Doe").setPhone("0234567890").setPosition(2);
contacts.add(contact2);
return contacts;
}
}