I've gone through several tutorials on Flutter and I find that they cover basics just fine but there are some nagging aspects of good design and good architecture that are consistently missing. I'm writing my first actual (not toy) application in Flutter and find myself running into these missing points.
Global data. Once a person installs the application and tries to use it, I ask them to log in / create an account, since this is an application specifically for managing groups of people. I'm using Firebase on the back end, and the package to do authentication winds up returning Future<FirebaseUser> from everything. So, yes, when it comes to building a Widget that uses the user's data, I can use a FutureBuilder. That said, it seems weird to have to keep typing boilerplate FutureBuilder code to dereference the user every place I want to use the user's ID to look up their data (what groups are they part of, what actions do they have pending, etc.). I really feel like there ought to be a way to invoke the future, get the actual user object, and then store it somewhere so that anything that wants a user ID for a query can just go get it. What's the right solution? I can't believe I'm the only person who has this problem.
Updatable data. I've got a page where I list the groups the current user is a member of. The user, though, can create a new group, join an existing group, or leave a group. When they do that, I need to redraw the page. The list of groups comes from running a Firebase query, so performing an action (join, leave, etc.) should signal the app to redraw the page, which will have the side effect of re-running the query. Conceivably, one might make the page dependent (how?) on the query results and have it redraw whenever they update, and instead have some widget somewhere that keeps track of the query. There's another answer here that hints that this might be the right way to go, but that's really concerned with relatively invariant data (locale doesn't change all that often for a single user). So, again, I can't believe I'm the only one who does this sort of thing. What's the best practice in this case?
Related
I could not think of how to best phrase my question, so what you think I'm asking is probably not what I'm asking. Pretend that I've got three working apps, but the needs of this fourth one are a little different, so I'm asking for advice / best practices, not how to write an SQL statement, code a listview, customize a viewcell, or how to bind to a datamodel. I've got all that.
The TLDR version:
What I need is a sane way to know when the user is done entering his data so I can push it to my server.
I know that there are people out there who have solved this problem, so I'm soliciting your advice. If you also did not want to do a save on every keystroke, I'm especially interested in your recommendations.
Here's the slightly more detailed version:
I'm writing a companion app for part of our desktop software, which uses a proprietary data server to store its data that I can access from the mobile device. In my previous apps, I have a screenful of controls. The user enters/modifies data, and I know to save to my local sqlite table when they exit the screen. It's basically a transactional model, and I only upload the data to our server when they press a button to do so. Works great.
However, for this app, instead of entering a screenful of information, the user is just entering numbers in a list. Think inventory: user has a list of products and quantities, and they update the quantities to match what they've got. The user will not leave the data entry screen often, and is likely to turn off the device, or kick the app out of memory without "going back", so my datamodels can have unstored data.
I have entry fields in a listview. I need to push the entered data to my sqlite table, then on to our proprietary data server. I would have thought to do it from the property setter in my datamodel, but sadly that gets called when the datamodel is initially bound, AND on every keystroke.
I know that there are lots of people who have coded data entry in a listview, I've read all the posts to figure out how make the viewcells work. I am primarily using Entry controls, and I have tried using TextChanged, Completed, and Unfocused. Each one has some sort of issue that makes it an undesirable solution "out of the box". I've got some ideas and know how to solves parts of my problem, but am hoping someone out there has a much better solution than my collection of little hacks.
Obviously there's a ton of details (and complexity) I'm leaving out. I am using a view - viewmodel - model architecture, but am not using MVVM or any similar framework. My solution needs to be in "vanilla" Xamarin Forms.
Thank you so much for your help!
I would comment but I don't have enough reputation, so here it goes.
Is there a specific reason a button can not be added to the page (below the list view), so that the user can press it when they are done taking "inventory"? I'm assuming this is part of the complexity/details you are leaving out.
Without a button, you could make the data save/push happen using an Unfocused event on each entry field. If the user filled in a value for each entry control, the unfocus event could call the method to save the data. The code-behind would get a bit cluttered and it could be messy if you have a long list of entry controls, but it would get the job done until you find (or create) or better way.
Hope this helps.
I've been thinking about the applications for goangular. In the need for immediate storage/database updates, such as a chat application or stocks application etc., I can see how goangular can be extremely useful in the sense of SignalR methodologies. But could it be applied to the traditional form with ten fields and a save button on it? All I could think of, was the traditional form, with ten fields on it -less the save button. If all ten fields are on the scope of the controller, than there would be no need for a save button. Every change of a field would be commemorated to the goinstant storage. Now having said that, how would one UNDO lets say any changes to those ten modified fields? Control+Z ten times? Not so robust. Any ideas on a UNDO all Changes button for such a form? (desperately trying to expand the bonds of real time database transactions)
I'll attempt to answer what I believe to be the spirit of your question first.
Most of the time, when using GoAngular, we're focused on synchronizing application state. Aka: Active clients sharing session data. Inevitably we drift into the territory of long-term persistence. At this point, rigorous validation / sanitization become a necessity, which we can't discuss without some context.
Let's say our user is completing their profile. This profile will be used to create a User model, which we will persist. Now that we have context, it becomes clear that we shouldn't persist a partially complete form, because it wouldn't represent a valid User model. We persist the form once it is complete, and valid.
Implementing this is as simple as creating a custom $scope.onSubmit method and validating the form input before calling $save on our new $scope.user model.
Undo would be easy to implement too, if you use $scope.users.$add, a key will be generated and returned, you could use this key to remove the new user. If you wanted to roll-back a change, you'd need to implement some system for versions, and roll back to the previous version of that User.
Hope I've answered your question in here somewhere :)
In most command interfaces I've seen, there is typically an "Execute" method which takes takes a command input and either returns void or some generic structure indicating if the command executed successfully or not (we are using the latter). Now, I've never thought of this before, but we suddenly got the need to know some more details about the result of the command than what you can expose generically.
Consider the following example:
you have a team and you are creating a screen where you can add members to your team. The members of the team are shown in a grid below the "add new member"-stuff. Now, when you press "add new member" you want to run some jquery/roundohuse/whatever and add the new member to the list of team members. No problems so far, but: you also want to include some identification data in a hidden field for each member and this id-data comes from the server.
So the problem is: how can I get that id-data from the server? The "AddNewTeamMember" command which I am pushing through the "ExecuteCommand"-method does not give me anything useful back, and if I add a new query method to the service saying something like: "GetLastAddedTeamMember" then I might just get the last entry added by someone else (at least if this is data which is very aggressively added by different users). In some situations you have a natural unique identifier generated on the client side which we can use, but for team members we did not.
Given that you have no choice but to update an on-page widget when another command completes, I see two choices for you:
Shoot off the command, display something locally that indicates it is submitted, and then wait until you get a notification from the server that the team member list has changed. Update the widget to reflect that.
Add a correlation ID to your command when you submit it, and add the team member provisionally locally to the list. When you get a confirmation from the server that a team member update happened because of your correlation ID, update your local data.
I would suggest the first approach, where the "provisional indicator" could be throwing a marked version of the normal indication into place; then, when you finally get an update you should have the data you need.
Given you went with CQRS to solve this problem I assume you have frequent updates to the content of those widgets happening in the background already, so have presumably solved the "background update" problem.
If not, I suggest you either ditch CQRS as a bad - over-complicated - solution in your problem space, or solve the background update problem first.
If you want to add an existing team member, you should query the read side of your application for this data. If you need to add a new team member, you have to consider if it's necessary to show the user in the grid below at once. Can you wait until the team member is in place on the read side? You can also query a service on the server side to get an unique ID (it can return a GUID). Then you add the team member to the grid, and of course, send the command to the server. But, if it's possible, try to design the application in a way that you don't have to show the team member at once. It's also possible to give the user a message saying something like this: "Team member added, waiting for response from server.". Then use AJAX to query the read side for new team members. When it appears on the read side, show it in the grid. You might have to deal with team members added by other users, but does it matter? CQRS gives you a great way to collaborate with other users, so maybe you should take advantage of that. As I see it; CQRS forces you to think different, and that may not be a bad thing.
I am currently drafting a concept for a (mostly) HTML-based collaboration suite which I plan to implement using CQRS. This software will contain messages that can be sent to the user (which can either be read or unread, obviously) and other elements which shall be marked "new" if they were created after the last user login.
Hardly something new, but I am not quite sure how that would be correctly implemented using CQRS. As I understand it, Change of any kind should, without exception, only be possible via Commands. But creating commands for every single (new) element that is being accessed seems a bit too much, not to mention the overhead.
I don't know if I need it, but what would be the best way to implement a Last-Accessed Timestamp on elements. Basically the same problem like the above, with the difference that the change happens EVERY time the element is accessed, not only the first time for each user.
CQRS seems to be an awesome concept but it really needs more learning material. Can't wait till a book is released :)
Regards
[Edit] No one? Wouldn't have thought that this is such a complicated issue..
I assume you're using event-sourcing in which case once you allow your query-service/event-handlers to raise appropriate events then this becomes fairly easy to solve.
For your messages/elements; when handling the specific creation events of your elements either add to existing or create additional event-handlers, to store to a messages read-model with a status of new and appropriate information about the element.
As part of you're user login I don't see why you can't raise a user-logged-in event (from the security/query service depending on how your implementing authentication) to say the user has logged in. An event-handler could capture this and write the last-login timestamp to a specific user-last-login read-model.
In addition the user-logged-in event-handler would need to update all the new messages (for that user) to an unread status. Seeing as we're changing the status of the messages as the user logs in do you still need to store the last-login timestamp?
For your last-accessed timestamp, perhaps you could just work this into your query service as queries for your different elements complete. Raise a query-completed event with element id/type information.
I'm using Catalyst with Catalyst::Plugin::Authentication and
Catalyst::Plugin::Authorization::Roles and am wondering if there is a better
approach to adding an attribute to a model that I'm not seeing.
Each user is permitted to access one or more companies, but there is
always one primary (current) company at a time. The permitted list is
stored in the database, and database access is primarily through DBIC.
My first inclination is to say that it's the user that has a current
company, and thus put it as part of the user model: give the user
package a "sub company { … }" to get/set the user's current company. The
database check is fairly easy; just use "$self->search_related" (a DBIC
method, inherited by the user model).
The problems I run in to are:
The current company needs to persist between requests, but I'd rather
not store it to the database (it should only persist for this
session). The natural place is the session…
There is a role, akin to Unix's root, that allows you to act as
any company, ignoring the list in the database. Checking this role
can be done through the database, but everywhere else in the app uses
$c->assert_user_role and friends.
I've heard its best to keep the models as Catalyst-independent as
possible. It also seems pretty weird to have a model manipulating
$c->session.
Of course, I could move those checks to the controllers, and have the
model accept whatever the controller sends, but that's violating DRY
pretty heavily, and just begging for a security issue if I forget one of
the checks somewhere.
Any suggestions? Or do I just shrug and go ahead and do it in the model?
Thanks, and apologies for the title, I couldn't come up with a good one.
The key is to create an instance of the model class for each request, and then pass in the parts of the request you need. In this case, you probably want to pass in a base resultset. Your model will make all the database calls via $self->resultset->..., and it will "just work" for the current user. (If the current user is root, then you just pass in $schema->resultset("Foo"). If the current user is someone else, then pass in $schema->resultset("Foo")->stuff_that_can_be_seen_by($c->user). Your model then no longer cares.)
I have some slides about this, but they are very outdated:
http://www.jrock.us/doqueue-grr/slide95c.html#end
(See the stuff immediately before and after, also.)
Note that restricted resultsets and web ACLs are orthogonal. You want to make the model as tight as possible (so that your app can't accidentally do something you don't want it to, even if the code says to), but various web-only details will still need to be encoded in ACLs. ("You are not allowed to view this page." is different from "You can only delete your own objects, not everyone's". The ACL handles the first case, the restricted resultset handles the second. Even if you write $rs->delete, since the resultset is restricted, you didn't delete everything in the database. You only deleted the things that you have permission to delete. Convenient!)