I'm developing an app for Android using Xamarin & MvvmCross. A simple version of View Models might look something like this:
public abstract class PersonViewModelBase
{
public string Name { get; set; }
}
public interface ICanRemove
{
MvxCommand RemoveCommand { get; }
}
public class LonelyPersonViewModel : PersonViewModelBase, ICanRemove
{
public bool IsHappy { get; set; }
public MvxCommand RemoveCommand { get { return new MvxCommand(() => RemoveFriend())); } }
private void RemoveFriend()
{
// Sends msg to remove a friend (using IMvxMessenger)
}
}
public class TiredPersonViewModel : PersonViewModelBase, ICanRemove
{
public int HoursSleepNeeded { get; set; }
// [ICanRemove implementation]
}
public class FriendlyPersonViewModel : PersonViewModelBase
{
public List<PersonViewModelBase> Friends { get; set; }
public MvxCommand AddFieldCommand
{
get
{
return new MvxCommand(
() => AddFriend());
}
}
private void AddFriend()
{
}
}
public class FriendsViewModel : MvxViewModel
{
public void Init()
{
Friends.Add(new LonelyPersonViewModel { Name = "Friend 1" });
Friends.Add(new TiredPersonViewModel { Name = "Friend 2" });
var friend3 = new FriendlyPersonViewModel { Name = "Friend 3" };
friend3.Friends.Add(new LonelyPersonViewModel { Name = "SubFriend 1" });
friend3.Friends.Add(new TiredPersonViewModel { Name = "SubFriend 2" });
Friends.Add(friend3);
Friends.Add(new LonelyPersonViewModel { Name = "Friend 4" });
var friend5 = new FriendlyPersonViewModel { Name = "Friend 5" };
var conjoinedFriend1 = new ConjoinedPersonViewModel { Name = "MultiPerson 1" };
conjoinedFriend1.CoPeople.Add(new TiredPersonViewModel { Name = "Jim" });
conjoinedFriend1.CoPeople.Add(new LonelyPersonViewModel { Name = "Nancy" });
friend5.Friends.Add(conjoinedFriend1);
Friends.Add(friend5);
}
public ObservableCollection<PersonViewModelBase> Friends { get; set; } = new ObservableCollection<PersonViewModelBase>();
}
The Friends view will be a ListView that displays the Friends as either a flat item or another list of friends. So it could look like this:
Friend 1
Friend 2
Friend 3 [Add]
SubFriend 1 [Del]
SubFriend 2 [Del]
Friend 4
Friend 5 [Add]
MultiPerson 1 [Del]
Jim
Nancy
The [Add] and [Del] are buttons that bind to the Add/Delete commands on the ViewModels. Currently, I'm using the CustomAdapter from the PolymorphicListView example, so each ViewModel is mapped to a custom layout. However, I don't currently have the full info displaying for FriendlyPersonViewModel or ConjoinedPersonViewModel.
I'm torn on how to continue:
I looked into doing nested listviews and found that it's not a good UX and I didn't go down through actually connecting the custom adapter to the child listviews.
I started the work to flatten the lists manually and having the Friends collection in the FriendsListView handle the Add/Remove. However, I need the subfriends to show up under the proper friend and they are currently just showing up at the bottom of the list, which is not idea, as the actual ViewModels are slightly more complex and would cause quite a bit of redundancy due to the FriendlyPeople names being repeated for each Friend added.
I looked at the old GroupedListView example from the Conference example, but each friend won't actually be in a group, some will be displayed alone.
Below is how I've got my current app working so far. The first Dark Blue section with the "Add" button was used to add the later dark blue sections; same for the Green sections. As I mentioned above, I'd like to have the later Dark Blue sections show up with the first Dark Blue section when I hit Add. I'd like for the first blue/green section to essentially be a header and have the next blue/green section directly below it; then hit add and have the next proper color show up under the right section without another header.
Does anyone have any suggestions or guidance on how to achieve this with Xamarin.Android with MVVMCross?
Thanks!
-g
Afterthought: It looks like there is an InsertItem on ObservableCollection, if I find the location of the last item added for the secrion I want to add the next PersonBase to, will the UI update properly? (Already left desk, or I'd check.)
UPDATE
Simply inserting the item using Insert(idx,item) will in fact update the UI properly and display the new item in the correct location. The next step would be, how do you style the items such that they are obviously a "group"?
Related
I'm currently learning Xamarin and are at the stage of examining ListView. I've noticed that if I bind ListView to a List and remove an item from it, the listview will not display this change. I get it that I need to use ObservableCollection to have it work automatically (or have a collection implement proper interface), but I just would like to understand why doesn't it work even when i reset the ItemsSource property of ListView after the removal. Here's the code:
namespace Lists
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class ListsOne : ContentPage
{
List<Contact> _contacts = new List<Contact> {
new Contact{ Name = "Mosh", Number = "1234566", ImageUrl="http://lorempixel.com/100/100/people/1"},
new Contact { Name = "Josh", Number = "1236578" , ImageUrl = "http://lorempixel.com/100/100/people/2"}};
IEnumerable<Contact> GetContacts(string searchText = null)
{
if (String.IsNullOrWhiteSpace(searchText))
{
return _contacts;
}
return _contacts.Where(c => c.Name.StartsWith(searchText));
}
public ListsOne()
{
InitializeComponent();
listView.ItemsSource = GetContacts();
}
private void Delete_Clicked(object sender, EventArgs e)
{
Contact contact = (sender as MenuItem).CommandParameter as Contact;
_contacts.Remove(contact);
listView.ItemsSource = GetContacts();
}
private void SearchBar_TextChanged(object sender, TextChangedEventArgs e)
{
listView.ItemsSource = GetContacts(e.NewTextValue);
}
}
}
As you can see in the Delete_Clicked method, after removing contact from _contacts, I reset the ItemsSource, but it will have no effect on the app, even though the pretty much same implementation for SearchBar_TextChanged works (if I recall correctly, am at work right now). Any insight on how it works? Sorry if it's dumb, but I am just learning.
Replace List with ObservableCollection. Latter one has event that notifies the UI about changes in it's array. (This is when you use MVVM, might not be applicable in in your example).
Also, as far as I know there is an issue with ListView.ItemSource when adding removing items to it. To make it work do this:
private void Delete_Clicked(object sender, EventArgs e)
{
Contact contact = (sender as MenuItem).CommandParameter as Contact;
_contacts.Remove(contact);
listView.ItemsSource = null;
listView.ItemsSource = GetContacts();
}
In the document of listview-ItemsSource, it says:
If you want the ListView to automatically update as items are added,
removed and changed in the underlying list, you'll need to use an
ObservableCollection. ObservableCollection is defined in
System.Collections.ObjectModel and is just like List, except that it
can notify ListView of any changes:
ObservableCollection<Employee> employees = new ObservableCollection<Employee>();
listView.ItemsSource = employees;
//Mr. Mono will be added to the ListView because it uses an ObservableCollection
employees.Add(new Employee(){ DisplayName="Mr. Mono"});
Help to realize the idea with the application. The application receives JSON from the site, and displays information on the screen (for example, games (image, name of the game, creators, year of release and description)). This is all parted, got the data through the retrofit and output them through recycleview. There is no problem with this, but I can’t think of a filter implementation. The filter must be dynamic, for example the creators and year of release. Activation opens where the CREATORS list goes down and the checkboxes with the studios name go down, and the YEAR of the ISSUE and the checkboxes with the year of release also go after it (only the creators and the year must take information from the data they received from the server via Jason). The idea is to have the first standard check box of the type all, which allows you to immediately output everything that is right at the start of the application, and then click the filter button and choose exactly what interests you. And there should be a button that all this update and returns with specific parameters. And I saw there is a button like a cross on the upper right (above on the actionbar), which possibly cancels everything and sets it back to its original position (all checkboxes only). I really hope for your advice and tips on how to implement this application. Thanks to all
Here is a good example of a filter (I need this one) https://pp.userapi.com/c851332/v851332451/e7308/hhiO3IOHPsg.jpg
in fact, a separate activity, which is dynamically filled with checkboxes on the results obtained from JSON (tobish the name, year, etc.)
POJO class value:
public class Value {
#SerializedName("title")
#Expose
private String title;
#SerializedName("year")
#Expose
private String year;
#SerializedName("genre")
#Expose
private List<String> genre;
#SerializedName("director")
#Expose
private String director;
#SerializedName("desription")
#Expose
private String desription;
#SerializedName("image")
#Expose
private String image;
public String getTitle() {
return title;
}
public String getYear() {
return year;
}
public List<String> getGenre() {
return genre;
}
public String getDirector() {
return director;
}
public String getDesription() {
return desription;
}
public String getImage() {
return image;
}
}
POJO class for list value:
public class Example {
#SerializedName("values")
#Expose
private List<Value> values = null;
public List<Value> getValues() {
return values;
}
}
MainActivity:
public class MainActivity extends Activity {
private static final String TAG = "MoviesApp";
RecyclerView recyclerView;
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu, menu);
return super.onCreateOptionsMenu(menu);
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.filters:
Intent intent = new Intent(this, FiltersActivity.class);
startActivity(intent);
return true;
default:
return super.onOptionsItemSelected(item);
}
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
recyclerView = findViewById(R.id.rc_movies);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
recyclerView.setLayoutManager(linearLayoutManager);
recyclerView.setHasFixedSize(true);
JSONPlaceHolderApi service = NetworkService.getJSONApi();
Call <Example> call = service.getValues();
call.enqueue(new Callback<Example>() {
#Override
public void onResponse(Call<Example> call, Response<Example> response) {
Example data = response.body();
if(response.isSuccessful()) {
if(data != null) {
MoviesAdapter moviesAdapter = new MoviesAdapter(MainActivity.this, data);
recyclerView.setAdapter(moviesAdapter);
Log.i(TAG, "Response call");
}else{
Log.i(TAG, "Data is null");
}
}else{
Log.i(TAG, "Response does not successful");
}
}
#Override
public void onFailure(Call<Example> call, Throwable t) {
Log.i(TAG, "Failure call");
}
});
}
}
And i cant make FiltersActivity so that it works according to the condition
Sorry for bad english :C
...
Hi Daniil,
using a second activity for filter is not a bad idea in my opinion.
The problem is to update the list every time you apply a new filter because you have to store your filters values somewhere and then update the list after apply.
I suggest you to try using a shared class between activities (maybe a ViewModel) and if you wanna allows the generation of filters you can build the interface of the second activity in a dynamic way during your network call using the values you have received.
All filters value can be stored inside the viewmodel into an appropriate object during the network call (using a background operation to not freeze the UI).
When you call the second activity the shared class will be interrogated to obtain the filters and in the onCreate method you can generate the corresponding Views programmatically from the data you have received after the interrogation.
Also,to make sure the "apply filter" event will be intercepted by the first activity you can use an Event Bus Library, a LiveData Object or an Observer Pattern (Listener) according to the ones that fit your architecture best. When the first activity receives the event you can force and offline filter operation in your data and update the RecyclerView using a notifyDataSetChange().
Another architectural approach is to use a single activity and two fragments, but it require more code despite it's flexibility.This approach fits best with Viewmodels and Livedata and allows you to manage only one activity lifecycle (no worries about the kill of the non focused activities by the system).
Hope this will give you some hints/help.
Cheers
`
public class moodspojo {
private int moodimg;
private String mood;
private boolean moodcheck;
public moodspojo(int moodimg, String mood,boolean moodcheck) {
this.moodimg = moodimg;
this.mood = mood;
this.moodcheck=moodcheck;
}
public int getMoodimg() {
return moodimg;
}
public String getMood() {
return mood;
}
public boolean isMoodcheck() {
return moodcheck;
}
}
I have two activities as shown below for which I have used recycler view. I want to save all the values to firebase which are checked by the user when user click on image button in the top right corner.
I want to save those checkbox values along with its image and name.
You can have boolean variable for check box in model, and use the value from model to check and unCheck checkBox in the RecyclerView.
When click on check box just flip the value in model also.
Add a public default constructor to your class public moodspojo(){/* No code needed */} and change the name of isMoodcheck() to getMoodcheck()
And then to store it into firebase, use this code
FirebaseDatabase.getInstance().getReference().setVaue(moodspojo.class);
You can read more about how to read and write to firebase realtime database over here https://firebase.google.com/docs/database/android/read-and-write
PS: Please follow convention. First letter of class should be capital
Thanks
I have the following case. We are using the Gherkin language to drive our native ui automation with Espresso. In our Gherkin lines we've got a line called:
And I tap on button "Next"
Where "Next" is a variable String we pass into our glue code (we are using the Cucumber framework).
As it happens, our app has many "Next" buttons with different resources ids. I ended up by using gherkin lines with variables like:
And I tap on button "Next in screen 1"
And I tap on button "Next in screen 2"
Now I want to use only the Gherkin variable "Next" in my code. I get an Integer array which contains all the resources ids from all the Next buttons, but when I want to check which id is displayed on the screen I got a NoMatchingViewException.
This is my current solution for now:
final int[] uiElementIds = getArrayWithIdsFromUIElement("Next");
int testId = 0;
for(int id : uiElementIds) {
try {
onView(withId(id)).check(matches(isDisplayed()));
testId = id;
break;
} catch(NoMatchingViewException ex) {
// do nothing
}
}
final int uiElementId = testId;
onView(withId(uiElementId)).withFailureHandler(new FailureHandler() {
#Override
public void handle(Throwable error, Matcher<View> viewMatcher) {
onView(withId(uiElementId)).perform(scrollTo(), click());
}
}).perform(click());
As you can see I just catch the thrown NoMatchingViewException and do nothing with it until it finds the right id and break out of the for-loop.
My question is:
Is there a better approach which we can use to loop through it to see which id is displayed and if so click on it?
In my mind I came up with this idea, but it is not integrated in Espresso:
for(int id : uiElementIds) {
if(onView(withId(id)).exist()) {
performClick();
}
}
According to your question
My question is:
Is there a better approach which we can use to loop through it to see which id is displayed and if so click on it?
In my mind I came up with this idea, but it is not integrated in
Espresso:
for(int id : uiElementIds) {
if(onView(withId(id)).exist()) {
performClick();
}
}
Here's my old Espresso framework Activity tests:
#RunWith(AndroidJUnit4.class)
#FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class DetailsActivityTest {
#Rule
public ActivityTestRule<MainActivity_> mRule = new ActivityTestRule<>(MainActivity_.class);
//TODO: Write tests to check if views are visible
private final int[] allDetailsActivityViewsIdis = {
R.id.dItem,
R.id.dDayAndDate,
R.id.dDay,
R.id.dDate,
R.id.dCity,
R.id.dTempAndIcon,
R.id.dTemperature,
R.id.dHighTemp,
R.id.dLowTemp,
R.id.dDescriptionLayout,
R.id.dDescription,
R.id.dForecast,
R.id.dHumidityLayout,
R.id.dHumidityDesc,
R.id.dHumidityVal,
R.id.dPressureLayout,
R.id.dPressureDesc,
R.id.dPressureVal,
R.id.dWindLayout,
R.id.dWindDesc,
R.id.dWindVal
};
private final int[] detailsActivityTextViewsIdis = {
R.id.dDay,
R.id.dDate,
R.id.dHighTemp,
R.id.dLowTemp,
R.id.dDescription,
R.id.dHumidityVal,
R.id.dPressureVal,
R.id.dWindVal,
};
private final int[] detailsActivityTextViewsDefaultValues = {
R.string.day,
R.string.date,
R.string.high_temp,
R.string.low_temp,
R.string.description,
R.string.humidity_val,
R.string.pressure_val,
R.string.wind_val,
};
#Before
//TODO: Rewrite this code using espresso-intents
public void checkIfAllDetailActivityViewsAreDisplayed() throws InterruptedException {
for (int viewId : allDetailsActivityViewsIdis)
onView(withId(viewId)).perform(click());
}
#Test
public void checkIfDetailsActivityViewsHaveNoDefaultValue() throws InterruptedException {
for (int viewId : detailsActivityTextViewsIdis)
for (int valueId : detailsActivityTextViewsDefaultValues)
onView(withId(viewId)).check(matches(not(withText(valueId))));
}
}
As you see, using foreach with Espresso is possible, but instead of exists() use check(matches(...) with value: isDisplayed, isDisplayedAtLeast or isCompletelyDisplayed
Hope it would help you
I'm writing Espresso tests for a navigation drawer that has a long, dynamic list of entries. I'd like to match a navDrawer menu item by Text and not position number. Looking at Google's DataAdapterSample, I would expect I could use this to get a match:
#Test
public void myTest() {
openDrawer();
onRow("Sign In").check(matches(isCompletelyDisplayed()));
}
private static DataInteraction onRow(String str) {
return onData(hasEntry(equalTo("module_name"), is(str)));
}
I'm not getting a match. But in the log I can see what I'm looking for. I get
No data found matching: map containing ["module_name"->is "Sign In"]
contained values: <[
Data: Row 0: {_id:"0", module_name:"Applications", module_secure:"false", headerCollapsible:1, } (class: android.database.MatrixCursor) token: 0,
Data: Row 1: {_id:"1", module_name:"Sign In", module_lock:"false", module_right_text:null, } (class: android.database.MatrixCursor) token: 1,
...
I think the hasEntry() works only for Maps, and it seems to me that items in your navigation drawer are not Maps but rather MatrixCursors.
Just replace the Person class in the example with MatrixCursor class.
For example something like this:
private static DataInteraction onRow(final String str) {
return onData(new BoundedMatcher<Object, MatrixCursor>(MatrixCursor.class) {
#Override
public void describeTo(Description description) {
description.appendText("Matching to MatrixCursor");
}
#Override
protected boolean matchesSafely(MatrixCursor cursor) {
return str.equals(cursor.getString(1));
}
});
}
Here I assume that second column of the cursor contains the text we need to match to. I am assuming this based on the "No data found matching.." error message.