I have two fragments, the first with a recyclerView to show all notes from a Room database and a second fragment where notes can be added, edited or deleted in the database. Navigation between the fragments is by JetPack Navigation. My problem is that after editing and saving a note in Room, when I navigate back to the List fragment, the recyclerView does not stay on the edited note. It defaults to the first item in the list. I am not sure how to inform the List fragment if a note was edited or added, and where it would be in the updated list.
In the Update Fragment, I add or edit note, and navigate back to List Fragment.
binding.btnSave.setOnClickListener {
val note = binding.etNote.text.trim().toString()
if (!args.isEditing){
//save a new note
val newNote = Note(0,note,date)
mNoteViewModel.addNote(newNote)
}else{
//save edited note
val updatedNote = Note(noteId,note,date)
mNoteViewModel.updateNote(updatedNote)
}
val action = UpdateDataDirections.actionUpdateDataToNoteList()
findNavController().navigate(action)
In the recycler adapter I have a function to update the note list.
var allNotes = listOf<Note>()
fun setData(notes:List<Note>){
this.allNotes = notes
notifyDataSetChanged()
}
In the List Fragment I have an observer to monitor changes to the database and update the recyclerView. The notes are sorted by date in my Room query. For now, I only need the recycler to scroll to the proper position when a note is edited, but it would also be useful if it could scroll to the position of a newly added note.
val adapter = NoteAdapter(viewClickListener)
val layoutManager = LinearLayoutManager(context)
binding.recycler.layoutManager = layoutManager
binding.recycler.setHasFixedSize(true)
binding.recycler.addItemDecoration(DividerItemDecoration(context,LinearLayoutManager.VERTICAL))
binding.recycler.adapter = adapter
mNoteViewModel.getAllNotes().observe(viewLifecycleOwner, {
it.let{
adapter.setData(it)
}
})
Related
I try the new Shell of Xamarin Form 4 for a small project.
I have a list of order, then someone chooses an order and start picking some inventory for this order with barcode. To be simple, I use 2 views and 2 viewmodel.
The process is:
1. User select an order the click a button "pickup"
2. I use ViewModelLocator (TinyIoc) to resolve the correct ViewModel of the pickup view
3. I call Initialize on the new ViewModel and pass some parameters needed. Like a list of items needed to be pickup.
4. Open the view in modal state.
I don't understand that if I change some qty in the list I pass on the second viewmodel, then hit the back button (modal view close). The quantity changed is now in the original viewmodel. How this is possible? I was thinking that passing parameter to a function do not share the same variable but just copy it??
Viewmodel of the first view (look at the Initialize function the List passed and the JobEnCours object)
private async Task GoPickup()
{
Device.BeginInvokeOnMainThread(async () =>
{
if (this.CodeJob != null && this.CodeJob != "")
{
this.IsBusy = true;
PrepChariotSP3ViewModel vm = ViewModelLocator.Resolve<PrepChariotSP3ViewModel>();
await vm.InitializeAsync(this._jobEnCours, this.ComposantesPick.ToList()) ;
await Shell.Current.Navigation.PushAsync(new PrepChariotSP3Page(vm));
this.IsBusy = false;
}
});
}
the the Initialize code on the second Viewmodel (look I set the JobEnCours.Description=Test)
public async Task InitialiseAsync(Job jobEnCours, List<ComposantePick> composantePick)
{
Title = "Ceuillette: " + jobEnCours.CodeProd;
this._jobEnCours = jobEnCours;
this.ComposantesPick = new ItemsChangeObservableCollection<ComposantePick>();
foreach(ComposantePick c in composantePick)
{
this.ComposantesPick.Add(c);
}
jobEnCours.Description = "test";
So, If I do the back button, then in the first VM the JobEnCours.Description is now set to "test" how this is possible?!
Any idea?
I have pursued the internet for an answer and following steps in apple documentation but to no avail.
I have two view controllers. One which contains a list of favourite items (FavouriteTableViewController), another which contains the details of the item being added to the list of favourites (ExplorationDetailViewController).
I have a save button on the navigation bar (from a navigation controller scene) in the ExplorationDetailViewController that I want to click to kick off the unwind segue. I have carried out the following steps, but when I click on the save button, it is completely unresponsive. I am including the most relevant code for the unwind.
I have created prepareforsegue function in ExplorationResultViewController from where I will be passing data for an image and a label to the other ViewController as part of the unwind segue
class ExplorationResultViewController: UIViewController {
var exploration_result: Favourites?
#IBOutlet weak var ExplorationLabel: UILabel!
#IBOutlet weak var ExplorationPhoto: UIImageView!
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Pass the selected object to the new view controller.
super.prepare(for: segue, sender: sender)
let photo = ExplorationPhoto.image
let name = ExplorationLabel.text ?? ""
exploration_result = Favourites(name: name, photo: photo)
}
Then in the FavouriteTableViewController, I have added an unwindtoHere method which adds image and label to the list.
class FavouritesTableViewController: UITableViewController {
// MARK: Properties
var favourites = [Favourites]()
// MARK: Actions
#IBAction func unwindToFavouriteList(sender: UIStoryboardSegue) {
if let sourceViewController = sender.source as? ExplorationResultViewController, let favourite = sourceViewController.exploration_result{
// Add a new meal.
let newIndexPath = IndexPath(row: favourites.count, section: 0)
favourites.append(favourite)
tableView.insertRows(at: [newIndexPath], with: .automatic)
}
Then going into my storyboard, I control+drag the Save button in the ExplorationDetailViewController and connect it to the exit item at the top of the scene. My unwind method appears in the pop up as: 'unwindToFavouriteListWithSender'. I select this.
I should be good to go now, but when I run the simulator and click the Save button, it is absolutely unresponsive. See StoryBoard Design
Please help
From your comments, I'm guessing that the problem is that there is no FavouritesTableViewController in the view controller hierarchy at the time that the Save button is pressed. You cannot unwind to something that isn't already present: "unwind" means go back to something that was instantiated earlier and remains present, above the current view controller in the view controller hierarchy.
For example, let's say we have
UINavigationController -> (root view controller) -> FavouritesTableViewController
-> (push) ExplorationResultViewController
Now we can unwind from the Exploration controller to the Favourites controller, because the Favorites controller is already in the hierarchy before the Exploration controller. It sounds like maybe you are not appreciating that fact.
I am building an application that will have the ability to create agenda items to discuss in a meeting. The agenda item might include one or more attachments to discuss so there is a one to many relation between the AgendaItems and the AgendaDocs models. So far, I have an insert form that looks like this:
The "Select File" button is a drive picker and the code I have inside the onDocumentSelect event is the following:
var docs = result.docs;
var createDataSource = app.datasources.AgendaDocs.modes.create;
for(var i=0; i<docs.length-1; i++){
var uniqueDraft = createDataSource.item;
createDataSource.items.push(uniqueDraft);
}
for(var i=0; i<createDataSource.items.length-1; i++){
var draft = createDataSource.item;
createDataSource.items[i].DocTitle = docs[i].name;
createDataSource.items[i].DocURL = docs[i].url;
createDataSource.items[i].DriveID = docs[i].id;
}
console.log(createDataSource.items);
The code is supposed to fill out the the List widget below the "Select File" button, but as how you see, the three items are the same. The datasource of the List widget is "AgendaDocs.modes.create" and the datasource of the insert form is "AgendaItems.modes.create".
Reading the official documentation from appmaker, makes me think it is possible since the properties of "CreateDataSource" includes "items". I need help from an expert here. Is this possible? Am I using the wrong approach?
First things first, it seems that you are trying to create records from different models and relationship between them in a one call... at this time App Maker is not that smart to digest such a complex meal. Most likely you'll need to break your flow into multiple steps:
Create (persist) Agenda Item
Create AgendaDocs records and relation with AgendaItem
Similar flow is implemented in Travel Approval template app, but it is not exactly the same as yours, since it doesn't create associations in batches.
Going back to the original question. Yep, it is possible to have multiple drafts, but not with the Create Datasource. You are looking for Manual Save Mode. Somewhere in perfect world your code would look similar to this:
// AgendaItems in Manual Save mode
var agendaDs = app.datasources.AgendaItems;
// this line will create item on client and automatically push it
// to ds.items and set ds.item to it.
agendaDs.createItem();
var agendaDraft = agendaDs.item;
// Field values can be populated from UI via bindings...
agendaDraft.Type = 'X';
agendaDraft.Description = 'Y';
// onDocumentSelect Drive Picker's event handler
var docsDs = agendaDs.relations.AgendaDocs;
result.docs.forEach(function(doc) {
// this line will create item on client and automatically push it
// to ds.items and set ds.item to it...however it will throw an exception
// with this message:
// Cannot save a foreign key association for the 'AgendaItem'
// relation because the target record has not been persisted
// to the server. To fix this, call saveChanges()
// on the data source for that record's model: AgendaItem
docsDs.createItem();
var docDraft = docsDs.item;
docDraft.DocTitle = doc.name;
docDraft.DocURL = doc.url;
docDraft.DriveID = doc.id;
});
// submit button click
agendaDraft.saveChanges();
Short Version: I want to have my Copy button in a table to be able to grab the values from an existing entry and populate those into a "Create Entry" Page Fragment. This way users don't have to reenter all the data when making a new entry.
Long Version:
I have two buttons added the rows in my table: Edit and Copy.
The Edit Button uses the following code to grab the information from that specific row and uses the Fragment to edit the entry.
widget.datasource.saveChanges();
app.datasources.SystemOrders.selectKey(widget.datasource.item._key);
app.showDialog(app.pageFragments.SystemOrders_Edit);
The Copy button is currently using the following code to duplicate the entry and automatically create it.
//Allows for copying table/row
var rowDataSource = widget.datasource;
var listDatasource = app.datasources.SystemOrders_HideComplete;
var createDataSource = listDatasource.modes.create;
widget.datasource.saveChanges();
// Enter fields you want to duplicate below
createDataSource.item.ProjectName = rowDataSource.item.ShowName;
createDataSource.item.DeliveryInfo = rowDataSource.item.DeliveryInfo;
createDataSource.item.SOB = rowDataSource.item.SOB;
createDataSource.item.DeliveryDate = rowDataSource.item.DeliveryDate;
createDataSource.item.Company = rowDataSource.item.Company;
createDataSource.item.Location = rowDataSource.item.Location;
createDataSource.item.AdditionalPeripherals = rowDataSource.item.AdditionalPeripherals;
createDataSource.item.Notes = rowDataSource.item.Notes;
createDataSource.createItem();
I would like to change this behavior so that the Copy button grab the values from those specific fields, however instead of doing a createDataSource/createItem(); I want it to place those values into a Page Fragment (ex: SystemOrders_Add) that has the corresponding fields.
This way the user can click "Copy" and the SystemOrders_Add Fragment appears with pre-populated values.
I want to make sure these values are only in the Page Fragment and do not get commited until the user presses the Submit button.
newSOEmailMessage(widget);
widget.datasource.createItem();
app.closeDialog();
Thank you for your help!
one way you can accomplish this is by passing the data to Custom Properties defined in your Page Fragment and then you can place those properties to the corresponding fields. I recommend you also check this article https://developers.google.com/appmaker/ui/viewfragments#use_custom_properties_to_customize_page_fragments
First you need to create the Custom Properties inside your Page Fragment. Then in your Copy button onClick event you can use something like this to save the row data from your table to the Custom Properties:
var rowDataSource = widget.datasource.item._key;
app.datasources.SystemOrders.selectKey(rowDataSource);
var projectName = app.datasources.SystemOrders.item.project_name;
var deliveryInfo = app.datasources.SystemOrders.item.delivery_info;
//...
app.pageFragments.SystemOrders_Edit.properties.ProjectName = projectName;
app.pageFragments.SystemOrders_Edit.properties.DeliveryInfo = deliveryInfo;
//...
app.showDialog(app.pageFragments.SystemOrders_Edit);
Assuming you have a form inside your Page Fragment, you can bind the value of each field with custom properties. Binding will ensure that the data is pre-populated. This can be done for each field via the Property Editor and the binding should look like this: #properties.ProjectName
Inside your Submit button onClick event you can use something like this to create a new item in the datasource using the values available in each field.
var projectName = widget.root.descendants.Field1.value;
var deliveryInfo = widget.root.descendants.Field2.value;
//...
var myDatasource = app.datasources.SystemOrders_HideComplete;
var myCreateDatasource = myDatasource.modes.create;
var draft = myDatasource.modes.create.item;
draft.project_name = projectName;
draft.delivery_info = deliveryInfo;
//...
// Create the new item
myCreateDatasource.createItem();
app.closeDialog();
You can set properties back to null once item is created (maybe onDetach) like this:
app.pageFragments.SystemOrders_Edit.properties.ProjectName = null;
Hope this helps!
I have a feeling that removing this line from the Copy Button click handler will make a trick(of course, if your page fragment is bound to ds.modes.create.item):
createDataSource.createItem();
In case, you are using Manual save mode and you are trying to reuse Page Fragment without overriding datasource... you need create new items using different approach:
// Copy Button click handler
var source = widget.datasource.item;
var listDatasource = app.datasources.SystemOrders_HideComplete;
// This line will add new item to the list datasource
// without saving it to database.
listDatasource.createItem();
var target = listDatasource.item;
// Enter fields you want to duplicate below
target.Field1 = source.Field1;
target.Field2 = source.Field1;
...
// Show fragment (assuming it is bound to listDatasource.item)
app.showDialog(app.pageFragments.EditItemFragment);
// -----------
// Page Fragment's Submit Button click handler
...
listDatasource.saveChanges(function() {
// TODO: handle successful save
});
Thank you to Pavel and Wilmar. The solution that worked for me is listed below:
//Allows for copying table/row
var rowDataSource = widget.datasource;
var listDatasource = app.datasources.SystemOrders_HideComplete;
var createDataSource = listDatasource.modes.create;
widget.datasource.saveChanges();
// Enter fields you want to duplicate below
createDataSource.item.ShowName = rowDataSource.item.ShowName;
createDataSource.item.DeliveryInfo = rowDataSource.item.DeliveryInfo;
createDataSource.item.SOB = rowDataSource.item.SOB;
createDataSource.item.Notes = rowDataSource.item.Notes;
app.datasources.SystemOrders.selectKey(widget.datasource.item._key);
app.showDialog(app.pageFragments.SystemOrders_Add);
I have a list of professional league of legends player components (wicket panels) displayed in a grid and for each player, they have their own modal view(also a wicket panel). My problem is on the initial load of the site, since all wicket panels are being created (both player and player-modal panels), the modal panel has a listview of player info and this is taking quite some time to load all players info and write it to a database table. Is there some way that I can make the query happen only when a player is clicked? I thought about putting the query in an AjaxEventBehavior, but i'm getting a NotSerialiableException on my dao. I also considered adding the modal panel ONLY when the player is clicked (also using ajaxeventbehavior) and I couldn't figure out how to dynamically add the html:
What is the best approach so I don't have to query the api/database on initial load of the site?
Here is the Players Panel and you can see I am adding the modalpanel when the player panel is selected:
public PlayerPanel(String id, final IModel<?> model) {
super(id, model);
Label proName = (new Label("proName", model));
proName.add(new AttributeModifier("data-target", "#modal" + model.getClass().toString()));
add(proName);
//initializing daos for player
ApplicationContext context = new ClassPathXmlApplicationContext("Spring-Module.xml");
ProPlayersDaoJdbc proPlayersDao = (ProPlayersDaoJdbc) context.getBean("proPlayersDaoJdbc");
ProGamesDaoJdbc proGamesDao = (ProGamesDaoJdbc) context.getBean("proGamesDaoJdbc");
ArrayList<Players> allPlayers = (ArrayList<Players>) proPlayersDao.listPlayers();
final ArrayList<Games> games = new ArrayList<Games>();
for(Players p : allPlayers){
if(p.getProName().equals(model.getObject().toString())){
region = p.getRegion();
player = p;
Image proImage = new Image("proImage", p.getThumbnailPath());
proImage.add(new AttributeModifier("src", p.getThumbnailPath()));
proImage.add(new AttributeModifier("data-target", "#modal" + model.getObject().toString()));
try {
ArrayList<Games> gamesList = updateGamesPlayed(player.getSummonerId());
for(Games game : gamesList){
games.add(game);
}
} catch (RiotApiException e) {
e.printStackTrace();
}
add(proImage);
}
}
if(!games.isEmpty()){
for(Games game : games){
proGamesDao.create(game);
}
}
add(new ModalPanel("modalPanel", model));
}
The updateGamesPlayed(...) method queries the API and the proGamesDao.create(game) writes it to the db table.
You could have player image/link on click method making database query, set the result as default model object of child panel, and have page or grid refresh via target.add. on the child panel, you could control the size.