I am having problem using INavigationAware codes. I have 3 pages. For example I named it pageA, pageB and pageC. PageA is a list view and I will pass the parameter to pageB using OnItemSelected
PageA View Model
public void OnItemSelected(Complaint item)
{
if (item != null)
{
var param = new NavigationParameters();
param.Add("id", item.Id);
mNavigationService.NavigateAsync("pageB", param);
}
}
In pageB, i will get the parameter using OnNavigatedTo.
PageB View Model
public async void OnNavigatedTo(NavigationParameters parameters)
{
var id = parameters["id"];
Title = string.Format("{0}: {1}", Strings.ComplaintDetail_Title, id);
await getComplaintDetail(Convert.ToInt32(id));
}
From pageB, I will send the parameter to pageC using the same way. But right now, I am having problem with passing the parameter back to pageB. Since I am using INavigation Back button on the left top, I don't know how to pass the parameter back to pageB. The issue is I need to pass the parameter (primary key) to all pages for select and update purposes. Please help me. I'm not sure how to pass the parameter using OnNavigatedFrom.
PageC View Model
public void OnNavigatedFrom(NavigationParameters parameters)
{
}
Thank you in advance.
I hate to state the obvious, but have you tried adding to the parameters collection in the OnNavigatedFrom method?
public void OnNavigatedFrom(NavigationParameters parameters)
{
parameters.Add("test", "testValue");
}
I Use Xamarin.Essentials SecureStorage to achieve this result, Just insert your value into SecureStorage with a key then get it again when needed :
Save your parameter when you navigate to PageB :
SecureStorage.SetAsync("current_id", YourParameterValue);
When back to BageB in your OnAppearing event (override the event) get value from SecureStorage :
protected override void OnAppearing()
{
base.OnAppearing();
var CurrentId = long.Parse( SecureStorage.GetAsync("current_id").Result);
this.BindingContext = new PageBViewModel(CurrentId) ;
}
Assuming that your Id is a long.
Note : i am not sure this is the clean way to do it but it works.
Related
A custom object that takes a parameter of (DocumentSnapShot documentsnapShot). also is an inner object from Firebase that retrieves a snapshot and set the values to my custom model also have its argument (DocumentSnapShot documentsnapShot). However, I wish to get the data from Firebase and pass it to my custom argument because mine takes multiple data not only Firebase. And it's not possible to iterate Firestore without an override.
Here's the code:
public UserSettings getUserSettings(DocumentSnapshot documentSnapshot){
Log.d(TAG, "getUserSettings: retrieving user account settings from firestore");
DocumentReference mSettings = mFirebaseFirestore.collection("user_account_settings").document(userID);
mSettings.get().addOnSuccessListener(new OnSuccessListener<DocumentSnapshot>() {
#Override
public void onSuccess(DocumentSnapshot documentSnapshot) {
UserAccountSettings settings = documentSnapshot.toObject(UserAccountSettings.class);
settings.setDisplay_name(documentSnapshot.getString("display_name"));
settings.setUsername(documentSnapshot.getString("username"));
settings.setWebsite(documentSnapshot.getString("website"));
settings.setProfile_photo(documentSnapshot.getString("profile_photo"));
settings.setPosts(documentSnapshot.getLong("posts"));
settings.setFollowers(documentSnapshot.getLong("followers"));
settings.setFollowing(documentSnapshot.getLong("following"));
}
});
}
You cannot return something now that hasn't been loaded yet. Firestore loads data asynchronously, since it may take some time for this. Depending on your connection speed and the state, it may take from a few hundred milliseconds to a few seconds before that data is available. If you want to pass settings object to another method, just call that method inside onSuccess() method and pass that object as an argument. So a quick fix would be this:
#Override
public void onSuccess(DocumentSnapshot documentSnapshot) {
UserAccountSettings settings = documentSnapshot.toObject(UserAccountSettings.class);
yourMethod(settings);
}
One more thing to mention is that you don't need to set the those values to object that already have them. You are already getting the data from the database as an object.
So remember, onSuccess() method has an asynchronous behaviour, which means that is called even before you are getting the data from your database. If you want to use the settings object outside that method, you need to create your own callback. To achieve this, first you need to create an interface like this:
public interface MyCallback {
void onCallback(UserAccountSettings settings);
}
Then you need to create a method that is actually getting the data from the database. This method should look like this:
public void readData(MyCallback myCallback) {
DocumentReference mSettings = mFirebaseFirestore.collection("user_account_settings").document(userID);
mSettings.get().addOnSuccessListener(new OnSuccessListener<DocumentSnapshot>() {
#Override
public void onSuccess(DocumentSnapshot documentSnapshot) {
UserAccountSettings settings = documentSnapshot.toObject(UserAccountSettings.class);
myCallback.onCallback(settings);
}
});
}
In the end just simply call readData() method and pass an instance of the MyCallback interface as an argument wherever you need it like this:
readData(new MyCallback() {
#Override
public void onCallback(UserAccountSettings settings) {
Log.d("TAG", settings.getDisplay_name());
}
});
This is the only way in which you can use that object of UserAccountSettings class outside onSuccess() method. For more informations, you can take also a look at this video.
Use LiveData as return type and observe the changes of it's value to execute desired operation.
private MutableLiveData<UserAccountSettings> userSettingsMutableLiveData = new MutableLiveData<>();
public MutableLiveData<UserAccountSettings> getUserSettings(DocumentSnapshot documentSnapshot){
DocumentReference mSettings = mFirebaseFirestore.collection("user_account_settings").document(userID);
mSettings.get().addOnSuccessListener(new OnSuccessListener<DocumentSnapshot>() {
#Override
public void onSuccess(DocumentSnapshot documentSnapshot) {
UserAccountSettings settings = documentSnapshot.toObject(UserAccountSettings.class);
settings.setDisplay_name(documentSnapshot.getString("display_name"));
settings.setUsername(documentSnapshot.getString("username"));
settings.setWebsite(documentSnapshot.getString("website"));
settings.setProfile_photo(documentSnapshot.getString("profile_photo"));
settings.setPosts(documentSnapshot.getLong("posts"));
settings.setFollowers(documentSnapshot.getLong("followers"));
settings.setFollowing(documentSnapshot.getLong("following"));
userSettingsMutableLiveData.setValue(settings);
}
});
return userSettingsMutableLiveData;
}
Then from your Activity/Fragment observe the LiveData and inside onChanged do your desired operation.
getUserSettings().observe(this, new Observer<UserAccountSettings>() {
#Override
public void onChanged(UserAccountSettings userAccountSettings) {
//here, do whatever you want on `userAccountSettings`
}
});
I'm trying to create a simple feature to make the first action act like the second one.
public IActionResult GetMessage()
{
return "message";
}
public IActionResult GetMessageDataModel()
{
return new MessageDataModel("message");
}
First idea came to my mind was to extend SystemTextJsonOutputFormater, and wrap context.Object with my data model in WriteResponseBodyAsync, but the action is marked sealed.
Then I tried to override WriteAsync but context.Object doesn't have protected setter, either.
Is there anyway I can achieve this by manipulating OutputFormatter?
Or I have another option instead of a custom OutputFormatter?
for some reason they prefer every response in a same format like {"return":"some message I write.","code":1}, hence I want this feature to achieve this instead of creating MessageDataModel every time.
Based on your description and requirement, it seems that you'd like to generate unified-format data globally instead of achieving it in each action's code logic. To achieve it, you can try to implement it in action filter, like below.
public class MyCustomFilter : Attribute, IActionFilter
{
public void OnActionExecuted(ActionExecutedContext context)
{
// implement code logic here
// based on your actual scenario
// get original message
// generate new instance of MessageDataModel
//example:
var mes = context.Result as JsonResult;
var model = new MessageDataModel
{
Code = 1,
Return = mes.Value.ToString()
};
context.Result = new JsonResult(model);
}
Apply it on specific action(s)
[MyCustomFilter]
public IActionResult GetMessage()
{
return Json("message");
}
I'm making a Windows Store App that asks for user input then produces a bunch of pushpins based on that input. When a pushpin is tapped the app navigates to a page with more detail.
Now the problem i'm having is this:
My pages all inherit from the automatically generated LayoutAwarePage so I could potentially make use of SaveState and LoadState to save the pushpins so they don't get wiped on navigation. The thing is that i can't get the pins to save into the Dictionary object supplied by SaveState.
The error I get is "Value cannot be null" and it's referring to the _pageKey variable in LayoutAwarePage.OnNavigatedFrom() and i don't know why it's happening.
I've tried serialising them into a JSON string so i can deserialise it in LoadState, but i get the same result using a string or a List of UIelement.
I think this is all due to my lack of understanding of how SaveState, LayoutAwarePAge and SuspensionManager work. I thought what i was doing would work as the Dictionary is only asking for a string and an object.
I'm not using any other methods from LayoutAwarePage so if there is a better way than using SaveState and LoadState, I'm all ears.
These are the two versions of SaveState i've tried:
Using JSON
protected override void SaveState(Dictionary<String, Object> pageState)
{
List<string> pindata = new List<string>();
List<string> serialisedpins = new List<string>();
foreach (Pushpin ele in map.Children)
{
pindata = ele.Tag as List<string>;
serialisedpins.Add(JsonConvert.SerializeObject(pindata));
}
string jasoned = JsonConvert.SerializeObject(serialisedpins);
pageState["pins"] = jasoned;
}
using a List of UIElement
protected override void SaveState(Dictionary<String, Object> pageState)
{
List<UIElement> pins = new List<UIElement>(map.Children);
pageState["pins"] = pins;
}
The error you're getting (_pagekey value cannot be null) is not really related to what you're saving into the Dictionary. The exception is most likely being thrown in OnNavigateFrom() method of LayoutAwarePage:
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
var frameState = SuspensionManager.SessionStateForFrame(this.Frame);
var pageState = new Dictionary<String, Object>();
this.SaveState(pageState);
frameState[_pageKey] = pageState; // <-- throws exception because _pageKey is null
}
If you take a look at the rest of the code of LayoutAwarePage you'll find out the value of _pageKey is being set in OnNavigatedTo method of LayoutAwarePage:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
// Returning to a cached page through navigation shouldn't trigger state loading
if (this._pageKey != null) return;
var frameState = SuspensionManager.SessionStateForFrame(this.Frame);
this._pageKey = "Page-" + this.Frame.BackStackDepth; <-- this line sets the _pageKey value
if (e.NavigationMode == NavigationMode.New)
{
// Clear existing state for forward navigation when adding a new page to the
// navigation stack
var nextPageKey = this._pageKey;
int nextPageIndex = this.Frame.BackStackDepth;
while (frameState.Remove(nextPageKey))
{
nextPageIndex++;
nextPageKey = "Page-" + nextPageIndex;
}
// Pass the navigation parameter to the new page
this.LoadState(e.Parameter, null);
}
else
{
// Pass the navigation parameter and preserved page state to the page, using
// the same strategy for loading suspended state and recreating pages discarded
// from cache
this.LoadState(e.Parameter, (Dictionary<String, Object>)frameState[this._pageKey]);
}
}
Usually the reason for that is that you're overriding OnNavigatedTo in your own page without calling base.OnNavigatedTo(e) inside it. The basic pattern of overriding it should always be:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
// the rest of your own code
}
This will make sure the base implementation will execute and set the _pageKey value as well as call LoadState() to load the previously saved state if there's any.
I am unsure on how I should be naming my View pages, they are all CamelCase.cshtml, that when viewed in the browser look like "http://www.website.com/Home/CamelCase".
When I am building outside of .NET my pages are named like "this-is-not-camel-case.html". How would I go about doing this in my MVC4 project?
If I did go with this then how would I tell the view to look at the relevant controller?
Views/Home/camel-case.cshtml
Fake edit: Sorry if this has been asked before, I can't find anything via search or Google. Thanks.
There are a few ways you can do this:
Name all of your views in the style you would like them to show up in the url
This is pretty simple, you just add the ActionName attribute to all of your actions and specify them in the style you would like your url to look like, then rename your CamelCase.cshtml files to camel-case.cshtml files.
Use attribute routing
Along the same lines as above, there is a plugin on nuget to enable attribute routing which lets you specify the full url for each action as an attribute on the action. It has convention attributes to help you out with controller names and such as well. I generally prefer this approach because I like to be very explicit with the routes in my application.
A more framework-y approach
It's probably possible to do something convention based by extending the MVC framework, but it would be a decent amount of work. In order to select the correct action on a controller, you'd need to map the action name on its way in to MVC to its CamelCase equivalent before the framework uses it to locate the action on the controller. The easiest place to do this is in the Route, which is the last thing to happen before the MVC framework takes over the request. You'll also need to convert the other way on the way out so the urls generated look like you want them to.
Since you don't really want to alter the existing method to register routes, it's probably best write a function in application init that loops over all routes after they have been registered and wraps them with your new functionality.
Here is an example route and modifications to application start that achieve what you are trying to do. I'd still go with the route attribute approach however.
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
WrapRoutesWithNamingConvention(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
AuthConfig.RegisterAuth();
}
private void WrapRoutesWithNamingConvention(RouteCollection routes)
{
var wrappedRoutes = routes.Select(m => new ConventionRoute(m)).ToList();
routes.Clear();
wrappedRoutes.ForEach(routes.Add);
}
private class ConventionRoute : Route
{
private readonly RouteBase baseRoute;
public ConventionRoute(RouteBase baseRoute)
: base(null, null)
{
this.baseRoute = baseRoute;
}
public override RouteData GetRouteData(HttpContextBase httpContext)
{
var baseRouteData = baseRoute.GetRouteData(httpContext);
if (baseRouteData == null) return null;
var actionName = baseRouteData.Values["action"] as string;
var convertedActionName = ConvertHyphensToPascalCase(actionName);
baseRouteData.Values["action"] = convertedActionName;
return baseRouteData;
}
private string ConvertHyphensToPascalCase(string hyphens)
{
var capitalParts = hyphens.Split('-').Select(m => m.Substring(0, 1).ToUpper() + m.Substring(1));
var pascalCase = String.Join("", capitalParts);
return pascalCase;
}
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
var valuesClone = new RouteValueDictionary(values);
var pascalAction = valuesClone["action"] as string;
var hyphens = ConvertPascalCaseToHyphens(pascalAction);
valuesClone["action"] = hyphens;
var baseRouteVirtualPath = baseRoute.GetVirtualPath(requestContext, valuesClone);
return baseRouteVirtualPath;
}
private string ConvertPascalCaseToHyphens(string pascal)
{
var pascalParts = new List<string>();
var currentPart = new StringBuilder();
foreach (var character in pascal)
{
if (char.IsUpper(character) && currentPart.Length > 0)
{
pascalParts.Add(currentPart.ToString());
currentPart.Clear();
}
currentPart.Append(character);
}
if (currentPart.Length > 0)
{
pascalParts.Add(currentPart.ToString());
}
var lowers = pascalParts.Select(m => m.ToLower());
var hyphens = String.Join("-", lowers);
return hyphens;
}
}
}
I have two view and their corresponding ViewModels and i want to send text from one view to another using MVVM Light as follows
in first viewmodel i am calling the following method
public void NavigatePage()
{
string temp = "temp value";
Messenger.Default.Send("temp");
Frame frame = Window.Current.Content as Frame;
if (frame != null) frame.Navigate(typeof(MyPage), temp);
}
while in page 2 view model i am having the following code
public MyViewModel()
{
Messenger.Default.Register<string>(this, MessageReceived);
}
private string test;
public string Test
{
get { return test; }
set { test = value; RaisePropertyChanged("Test");}
}
private void MessageReceived(string message)
{
Test = message;
}
when i debug my code the ctor of this viewmodel is getting called but the MessageReceived is not getting called hence property Test is never getting set, I am missing something, please help
Is the SecondViewModel actually created before you send the message? You can specify this in the ViewModelLocator class.
In the locator you have to register your viewmodel and CREATE it when the applications starts.
Like this:
SimpleIoc.Default.Register<SecondViewModel>(true);
With the true parameter the SecondViewModel will be created when the application is started! :)