Unity. Can't activate scene instance when it cached after download - c#

I have Main menu provider:
class MainMenuProvider
{
private static SceneInstance _cachedScene;
private static bool _isLoaded;
public async Task<SceneInstance> Load()
{
if (!_isLoaded)
{
var mainMenuLoadHandle =
Addressables.LoadSceneAsync("Assets/Bundles/UI/Scenes/MainMenu.unity", LoadSceneMode.Single, false);
await mainMenuLoadHandle.Task;
_cachedScene = mainMenuLoadHandle.Result;
_isLoaded = true;
}
return _cachedScene;
}
}
When I firstly invoke Load and then scene.ActivateSync it works perfect, but when I invoke Load and ActivateAsync the second time and my scene is cached, nothing happens.
_cachedScene.m_Operation.isDone == true

You set _isLoaded static so this whole thing is definitely only executed exactly once the entire application session lifecycle.
However, if you then ever happen to use LoadSceneMode.Single for any other scene at any time, then this _cachedScene is unloaded and doesn't exist anymore.
=> _cachedScene will then contain an invalid scene which isn't loaded anymore and will never be loaded again.
You would need to decouple the loading of the addressable and the actual loading of the scene. The Addressables.LoadSceneAsync already contains directly also the call to SceneManagement.SceneManager.LoadSceneAsync. You want to split that and only use e.g. only Addressables.LoadAssetAsync for your caching.
And then only load and unload this scene via the mentioned SceneManagement.SceneManager.LoadSceneAsync somewhat like e.g.
class MainMenuProvider
{
private static AsyncOperationHandle<Scene>? _sceneHandle;
private static Scene? _loadedScene;
public async Task<Scene> Load()
{
if(TryGetLoadedScene(out var scene))
{
return scene;
}
if (_sceneHandle == null)
{
_sceneHandle = Addressables.LoadAssetAsync<Scene>("Assets/Bundles/UI/Scenes/MainMenu.unity");
}
await _sceneHandle.Task;
if(TryGetLoadedScene(out var scene))
{
return scene;
}
var path = _sceneHandle.Result.path;
await SceneManager.LoadSceneAsync(path, LoadSceneMode.Single, false);
_loadedScene = SceneManager.GetSceneByPath(path);
return _loadedScene;
}
private static bool TryGetLoadedScene(out Scene? loadedScene)
{
if(_loadedScene == null)
{
loadedScene = null;
return false;
}
loadedScene = SceneManager.GetSceneByPath(_loadedScene.path);
if(scene.isValid)
{
_loadedScene = loadedScene;
return true;
}
return false;
}
}
This would to certain extent reduce the advantage of the addressable of course since one of them is less memory usage by unloading the scene while it isn't used.

Related

How to erase all Xamarin.Forms.Application.Current.Properties?

I'm creating a Helper to find out if the user is using my app for the first time (not only), that's why I'm using Xamarin.Forms.Aplication.Current.Properties. When the user logs out, the app will remove all keys, include that. But this is not happening and I don't know what.
My code:
public static class FirstUseAppHelper
{
public static string FIRST_USE = "FirstUse";
public static void Initialize()
{
if (Application.Current.Properties.ContainsKey(FIRST_USE))
{
Application.Current.Properties[FIRST_USE] = false;
}
else
{
Application.Current.Properties[FIRST_USE] = true;
}
}
public static bool CheckFirstUseApp()
{
if (!Application.Current.Properties.ContainsKey(FIRST_USE))
throw new Exception("Key not found.");
else
return Application.Current.Properties[FIRST_USE].Equals(true);
}
}
When the app starts (App.Xaml.cs) is called the method:
protected override async void OnInitialized()
{
InitializeComponent();
FirstUseAppHelper.Initialize();
...
}
Logout method (This is not working):
LogOutCommand = new DelegateCommand(() =>
{
var keys = Application.Current.Properties.Keys.ToList();
foreach (var k in keys)
Application.Current.Properties[k] = null;
Application.Current.SavePropertiesAsync();
}
I understand that the code is correct, but for some reason the keys are not being erased. Has anyone had this problem or has a better solution?
This is because the automatic backup of the latest versions of Android.
You need to add android:allowBackup="false" and android:fullBackupContent="false" to the tag in AndroidManifest.xml
if you don't want to back up your data or if you want to include or exclude some features.

How to create loading in Unity until load information?

hi,i found a code for loading scene in unity3D, I want to take when data via PHP (through WWW), Loading displayed, please help me.
Also, how to recognize and change the keyboard language for Android Device?
void Update(){
if (loadScene == false)
{
loadScene = true;
loadingText.text = "Is loading your information...";
StartCoroutine(LoadNewScene());
}
if (loadScene == true)
{
loadingText.color = new Color(loadingText.color.r, loadingText.color.g, loadingText.color.b, Mathf.PingPong(Time.time, 1));
}
}
}
void Start(){
StartCoroutine(rankGet());
}
IEnumerator LoadNewScene() {
yield return new WaitForSeconds(3);
AsyncOperation async = Application.LoadLevelAsync(scene);
while (!async.isDone) {
yield return null;
}
}
IEnumerator rankGet()
{
txtUsername.text = PlayerPrefs.GetString("username");
WWW connection = new WWW("http://127.0.0.1/scoregame/userscorerank.php?uss=" + txtUsername.text);
yield return (connection);
if (connection.text == "401")
{
//Debug.Log("username do not exits!");
}
else
{
ranktxt.text = connection.text;
}
}
The easiest way to use Async operation for any other purpose then loading new scene, is to
write your own method that produces an IEnumerable instance.
As described here by msknapp user.
public System.Collections.IEnumerable coroutineFunction()
{
while (!doneYourPersistentProcess())
// ... wait for it ...
yield return "";
// now it is done.
doPostProcessing();
}
public void doPostProcessing()
{
// Loading progressbar here
}
public void Update()
{
if (userStartsPersistentProcess())
StartCoroutine(coroutineFunction());
}
public bool userStartsPersistentProcess()
{
// your code here.
}
public bool doneYourPersistentProcess()
{
// your code here.
}
And the last step is to prepare graphic for progress bar.
The easiest way is to change Fill Amount value in the Image object, with properties as shown below:

Prism NavigationService get previous view name

Currently I'm implementing a Screen indicating wheater a module is not existing or still in development.
The Back Button has the following code:
regionNavigationService.Journal.GoBack();
This is working as expected. But the user is not coming from the Home Screen. So I need to access the View Name from the last Entry in Navigation Journal.
Example: User is coming from Settings Screen => The text should display "Back to Settings Screen"
Assuming the view name you are looking for is when you do new Uri("Main", UriKind.Relative) that you would want the word Main as the view name.
The forward and backward stacks in the RegionNavigationJournal are private. You could use reflection to get access to it.
var journal = regionNavigationService.Journal as RegionNavigationJournal;
if (journal != null)
{
var stack =
(Stack<IRegionNavigationJournalEntry>)
typeof (RegionNavigationJournal).GetField("backStack",
BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(journal);
var name = stack.Peek().Uri.OriginalString;
}
Or a better way is to implement your own IRegionNavigationJournal that is a wrapper around it. This is using Unity to constructor inject the default RegionNavigationJournal if using MEF you might need to put the ImportingConstructorAttribute on it.
public class RegionNavigationJournalWrapper : IRegionNavigationJournal
{
private readonly IRegionNavigationJournal _regionNavigationJournal;
private readonly Stack<Uri> _backStack = new Stack<Uri>();
// Constructor inject prism default RegionNavigationJournal to wrap
public RegionNavigationJournalWrapper(RegionNavigationJournal regionNavigationJournal)
{
_regionNavigationJournal = regionNavigationJournal;
}
public string PreviousViewName
{
get
{
if (_backStack.Count > 0)
{
return _backStack.Peek().OriginalString;
}
return String.Empty;
}
}
public bool CanGoBack
{
get { return _regionNavigationJournal.CanGoBack; }
}
public bool CanGoForward
{
get { return _regionNavigationJournal.CanGoForward; }
}
public void Clear()
{
_backStack.Clear();
_regionNavigationJournal.Clear();
}
public IRegionNavigationJournalEntry CurrentEntry
{
get { return _regionNavigationJournal.CurrentEntry; }
}
public void GoBack()
{
// Save current entry
var currentEntry = CurrentEntry;
// try and go back
_regionNavigationJournal.GoBack();
// if currententry isn't equal to previous entry then we moved back
if (CurrentEntry != currentEntry)
{
_backStack.Pop();
}
}
public void GoForward()
{
// Save current entry
var currentEntry = CurrentEntry;
// try and go forward
_regionNavigationJournal.GoForward();
// if currententry isn't equal to previous entry then we moved forward
if (currentEntry != null && CurrentEntry != currentEntry)
{
_backStack.Push(currentEntry.Uri);
}
}
public INavigateAsync NavigationTarget
{
get { return _regionNavigationJournal.NavigationTarget; }
set { _regionNavigationJournal.NavigationTarget = value; }
}
public void RecordNavigation(IRegionNavigationJournalEntry entry)
{
var currentEntry = CurrentEntry;
_regionNavigationJournal.RecordNavigation(entry);
// if currententry isn't equal to previous entry then we moved forward
if (currentEntry != null && CurrentEntry == entry)
{
_backStack.Push(currentEntry.Uri);
}
}
}
If using unity in your Prism Bootstrapper you will need to replace the default registration of the IRegionNavigationJournal
protected override void ConfigureContainer()
{
this.RegisterTypeIfMissing(typeof(IRegionNavigationJournal), typeof(RegionNavigationJournalWrapper), false);
base.ConfigureContainer();
}
If using MEF you will need to put the ExportAttribute on top of the RegionNavigationJournalWrapper
[Export(typeof(IRegionNavigationJournal))]
You can see http://msdn.microsoft.com/en-us/library/gg430866%28v=pandp.40%29.aspx for more information on replacing their default implementation with your own. Once you have the wrapper you will still need to cast it as RegionNavigationJournalWrapper to get access to the PreviousViewName so still not perfect or create an interface that RegionNavigationJournalWrapper also implements to cast to that to get you access to the PreviousViewName

How do I update the parent viewmodel when child viewmodel is updated

In my first view model (renamed to MainViewModel) I have a list of ActionViewModels.
In my xaml i have a listbox which is bound to the list, in the listbox i have a template which binds to properties from the ActionViewModel.
So far so good and everything works.
When selecting one of the listitems i navigate to an ActionViewModel and pass the id with it.
The ActionViewModel retrieves information from a static list in memory from which the MainViewModel also retrieved the information to create the list of actionviewmodels.
So far still so good, i can edit the properties, all the bindings do work fine and i'm all happy.
By clicking the save button the information is gathered and stored in the static list.
When i hit the back button i go back to the list, but unfortunately the values showing there are still the same, is there some way to send a command to reload the items in the list? To pass a complete viewmodel as reference to a new ActionViewModel? Or some property which tells the parent 'this viewmodel in your list has been updated'?
I am sure the above text is a bit confusing, so here is some code to clarify it a bit (hopefully)
MainViewModel.cs
private List<ActionViewModel> _actionViewModels;
public List<ActionViewModel> ActionViewModels
{
get { return _actionViewModels; }
set { _actionViewModels = value; RaisePropertyChanged(() => ActionViewModels); }
}
private Cirrious.MvvmCross.ViewModels.MvxCommand<int> _navigateToAction;
public System.Windows.Input.ICommand NavigateToAction
{
get
{
_navigateToAction = _navigateToAction ?? new Cirrious.MvvmCross.ViewModels.MvxCommand<int>((action) => NavigateToTheDesiredAction(action));
return _navigateToAction;
}
}
private void NavigateToTheDesiredAction(int action)
{
ShowViewModel<ActionViewModel>(new { id = action });
}
// Get DTOs from server or from cache and fill the list of ActionViewModels
public async Task Load()
{
ActionService actionService = new ActionService();
List<ActionViewModel> actionViewModels = new List<ActionViewModel>();
MyActions = await actionService.GetMyActions();
foreach (ActionDTO action in MyActions)
{
ActionViewModel actionViewModel = new ActionViewModel();
await actionViewModel.Load(action.id);
actionViewModels.Add(actionViewModel);
}
ActionViewModels = actionViewModels;
}
ActionViewModel.cs
public int ID
{
get { return TheAction.id; }
set { TheAction.id = value; RaisePropertyChanged(() => ID); }
}
public string Title
{
get { return TheAction.Title; }
set { TheAction.Title = value; RaisePropertyChanged(() => Title); }
}
public async Task Load(int actionId)
{
ActionDTO TheAction = await actionService.GetAction(actionId);
this.ID = TheAction.id;
this.Title = TheAction.Title;
}
private Cirrious.MvvmCross.ViewModels.MvxCommand _save;
public System.Windows.Input.ICommand Save
{
get
{
_save = _save ?? new Cirrious.MvvmCross.ViewModels.MvxCommand(PreSaveModel);
return _save;
}
}
private void PreSaveModel()
{
SaveModel();
}
private async Task SaveModel()
{
ValidationDTO result = await actionService.SaveAction(TheAction);
}
ActionService.cs
public static List<ActionDTO> AllActions = new List<ActionDTO>();
public async Task<ActionDTO> GetAction(int actionId)
{
ActionDTO action = AllActions.FirstOrDefault(a => a.id == actionId);
if (action == null)
{
int tempActionId = await LoadAction(actionId);
if (tempActionId > 0)
return await GetAction(actionId);
else
return new ActionDTO() { Error = new ValidationDTO(false, "Failed to load the action with id " + actionId, ErrorCode.InvalidActionId) };
}
return action;
}
private async Task<int> LoadAction(int actionId)
{
ActionDTO action = await webservice.GetAction(actionId);
AllActions.Add(action);
return action.id;
}
public async Task<ValidationDTO> SaveAction(ActionDTO action)
{
List<ActionDTO> currentList = AllActions;
ActionDTO removeActionFromList = currentList.FirstOrDefault(a => a.id == action.id);
if (removeActionFromList != null)
currentList.Remove(removeActionFromList);
currentList.Add(action);
AllActions = currentList;
return await webservice.SaveAction(action);
}
There are 3 ways I can think of that would allow you to do this.
The ActionService could send out some sort of notification when data changes. One easy way to do this is to use the MvvmCross Messenger plugin. This is the way the CollectABull service works in CollectionService.cs in the N+1 days of mvvmcross videos (for more info watch N=13 in http://mvvmcross.wordpress.com)
This is the approach I generally use. It has low overhead, uses WeakReferences (so doesn't leak memory), it is easily extensible (any object can listen for changes), and it encourages loose coupling of the ViewModel and Model objects
You could implement some kind of Refresh API on the list ViewModel and could call this from appropriate View events (e.g. ViewDidAppear, OnNavigatedTo and OnResume).
I don't generally use this approach for Refreshing known data, but I have used it for enabling/disabling resource intensive objects - e.g. timers
For certain shape of model data (and especially how often it changes), then I can imagine scenarios where this approach might be more efficient than the messenger approach.
You could extend the use of INotifyPropertyChanged and INotifyCollectionChanged back into your model layer.
I've done this a few times and it's worked well for me.
If you do choose this approach, be careful to ensure that all Views do subscribe to change events using WeakReference subscriptions such as those used in MvvmCross binding - see WeakSubscription. If you didn't do this, then it could be possible for the Model to cause Views to persist in memory even after the UI itself has removed them.

How to Force Disposal of Objects / GC

How do you force objects to dispose after use in order to free up memory? And, how do you force GC to collect?
Here's my Save Code. I've noticed that, every time I execute this function, my memory consumption goes up that would eventually caused an out of memory error after a couple of hits.
protected void btnSaveEmptyOC_Click(object sender, EventArgs e)
{
try
{
if (ViewState["ServiceDetailID"].ToString() != null)
{
CashExpense tblCashExpenses = new CashExpense();
Guid CashExpensesID = Guid.NewGuid();
tblCashExpenses.CashExpensesID = CashExpensesID;
tblCashExpenses.ServiceDetailsID = new Guid(ViewState["ServiceDetailID"].ToString());
tblCashExpenses.Description = txtDescriptionEmptyOC.Text;
tblCashExpenses.Quantity = Decimal.Parse(txtQTYEmptyOC.Text);
tblCashExpenses.UnitCost = Decimal.Parse(txtUnitCostEmptyOC.Text);
tblCashExpenses.CreatedBy = User.Identity.Name;
tblCashExpenses.DateCreated = DateTime.Now;
tblCashExpenses.CashExpensesTypeID = "OTHER";
CashExpenses_worker.insert(tblCashExpenses);
CashExpenses_worker.submit();
//Clear items after saving
txtDescriptionEmptyOC.Text = "";
txtQTYEmptyOC.Text = "";
txtUnitCostEmptyOC.Text = "";
ValidationMessage.ShowValidationMessage(MessageCenter.CashExpenseMaintenace.InsertOC2, "SaveEmptyOC", this.Page);
MyAuditProvider.Insert(this.GetType().ToString(), ViewState["MarginAnalysisID"].ToString(), MessageCenter.Mode.ADD, MessageCenter.CashExpenseMaintenace.InsertOC2, Page.Request, User);
divOtherCost.Visible = false;
grd_othercost.Visible = true;
btnaddothercost.Visible = true;
tblCashExpenses = null;
}
else
{
ValidationMessage.ShowValidationMessage(MessageCenter.CashExpenseMaintenace.SaveServiceDetailOC, "SaveEmptyOC", this.Page);
}
}
catch
{
ValidationMessage.ShowValidationMessage(MessageCenter.CashExpenseMaintenace.InsertOCError, "SaveEmptyOC", this.Page);
}
finally
{
//Rebinds the Grid
populategrd_othercost();
Dispose();
GC.SuppressFinalize(this);
}
}
Here's My business layer class
public class CashExpensesBL
{
CEADataStoreDataContext CashExpensesDB = new CEADataStoreDataContext();
public IEnumerable<CashExpense> get()
{
return CashExpensesDB.CashExpenses;
}
public IEnumerable<CashExpense> get(Expression<Func<CashExpense, Boolean>> express)
{
return CashExpensesDB.CashExpenses.Where(express);
}
public void insert(CashExpense item)
{
CashExpensesDB.CashExpenses.InsertOnSubmit(item);
}
public void delete(CashExpense item)
{
CashExpensesDB.CashExpenses.DeleteOnSubmit(item);
}
public void deleteDC(Guid servicedetailid)
{
CashExpensesDB.sp_deleteDefaultCost(servicedetailid);
}
public void submit()
{
CashExpensesDB.SubmitChanges();
}
}
You should dispose your DataContext. I can't see it being removed anywhere, so the connection will remain open and references may be held on to (preventing the GC from picking them up). This may be what's causing the problem. If you don't want to dispose manually, you can perform the transaction within a using block.
Edit in response to Business Layer update -
You can wrap the methods in using blocks like this:
public void insert(CashExpense item)
{
using(CEADataStoreDataContext CashExpensesDB = new CEADataStoreDataContext())
{
CashExpensesDB.CashExpenses.InsertOnSubmit(item);
CashExpensesDB.SubmitChanges();
}
}
Assign nulls to variables referencing your objects, the use GC.Collect(); to force garbage collection. You may need to call it twice in a row to speed non-accessible objects through the process.
Set the object to null, then call:
GC.Collect();
GC.WaitForPendingFinalizers();

Categories