Search code examples
androidxamarinandroid-fragmentsandroid-recyclerviewnotifydatasetchanged

Android Xamarin c# Cannot Update Views After Replacing Fragment


I have a fragment which is handling "crafting" of items, it displays a "crafting bench", a cost list of items and an inventory. At the first initialization of the fragment, the crafting bench works fine but after I replace it back and fourth, it does not update when I craft (click the craft button).

Heres an example: Example Video

Ive tried wrapping some functions with Handler.Post but it didnt work.

The fragment:

using Android.Graphics;
using Android.OS;
using Android.Views;
using Android.Widget;
using AndroidX.Fragment.App;
using MineClick.Crafting.Bench;
using MineClick.Crafting.Bench.CostList;
using MineClick.EventSystem;
using MineClick.EventSystem.EventArguments;
using MineClick.Materials;
using MineClick.Navigation;
using MineClick.Pickaxes;
using MineClick.Player;
using MineClick.Utility;
using static MineClick.EventSystem.EventManager;
using static MineClick.InventoryView;

namespace MineClick.Crafting
{
    internal class CraftFragment : Fragment
    {

        CraftAdapter CraftAdapter { get; set; }

        public override void OnCreate(Bundle savedInstanceState)
        {
            base.OnCreate(savedInstanceState);



        }

        public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
        {
           
            View view = inflater.Inflate(Resource.Layout.crafting_activity, container, false);
            LinearLayout wrraper = view.FindViewById<LinearLayout>(Resource.Id.crafting_wrraper);
            LinearLayout inventoryWrraper = view.FindViewById<LinearLayout>(Resource.Id.inventory_wrraper);

            LinearLayout costListWrraper = view.FindViewById<LinearLayout>(Resource.Id.cost_list_wrraper);
           
            CraftingBench bench = new CraftingBench(Context);
            wrraper.AddView(bench);

            InventoryView inventoryDisplay = new InventoryView(Context, PlayerApplication.Instance.Player.PlayerInventory);
            inventoryWrraper.AddView(inventoryDisplay);
            CostListView costList = new CostListView(Context);
            costListWrraper.AddView(costList);
            CraftAdapter = new CraftAdapter(inventoryDisplay, bench,costList);
            return view;
        }

    }
}

The Craft Adapter:

using Android.Widget;
using MineClick.Crafting.Bench.CostList;
using MineClick.Crafting.Recipes.Handlers;
using MineClick.EventSystem;
using MineClick.Materials;
using MineClick.Player;
using static MineClick.EventSystem.EventManager;
using static MineClick.InventoryView;

namespace MineClick.Crafting
{
    internal class CraftAdapter
    {

        InventoryView InvView;
        RecipeAdapter BenchAdapter;
        public CraftAdapter(InventoryView invView, CraftingBench bench, CostListView costList)
        {
            InvView = invView;
            BenchAdapter = new RecipeAdapter(bench,costList);
            Initialize();
        }

        private void Initialize()
        {
            InvView.OnClickActions = OnInvClick;
            EventManager.BindListener(Events.ITEM_CRAFTED, (sender, args) => OnCraft(args.CraftedItem));
        }

        public void OnInvClick(InventoryClickEvent e)
        {
            if (e.ClickedItem.DisplayedItem == null)
                return;
            Item itemToAdd = ItemRegistry.GetItemById(e.ClickedItem.DisplayedItem.GetItem().Id);

            bool add = BenchAdapter.OnAttemptAddItem(itemToAdd.Clone());

            // Do something if add failed (toast or something)
            if(!add)
                Toast.MakeText(e.ClickedItem.Context, "Cannot Add Item To Bench",ToastLength.Long);
            
        }

        public void OnCraft(Item item)
        {
            PlayerApplication.Instance.Player.PlayerInventory.Add(item);
            BenchAdapter.Reset();
            InvView.Update();
            
        }
    }
}

The recipe adapter:

using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using MineClick.Crafting.Bench;
using MineClick.Crafting.Bench.CostList;
using MineClick.EventSystem;
using MineClick.EventSystem.EventArguments;
using MineClick.Materials;
using MineClick.Player;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using static MineClick.EventSystem.EventManager;

namespace MineClick.Crafting.Recipes.Handlers
{
    internal class RecipeAdapter
    {
        public CraftingBench Bench { get; set; }
        public RecipeManager Manager { get; set; }

        public CostListView CostList;
        public RecipeAdapter(CraftingBench bench, CostListView costList,Item defaultMainItem,Item[] defaultExtras)
        {
            Manager = new RecipeManager(defaultMainItem,defaultExtras);
            Bench = bench;
            CostList = costList;
            CostList.CButton.Click += (sender, args) => Craft();
            Update();
        }

        public RecipeAdapter(CraftingBench bench, CostListView costList)
        {
            Manager = new RecipeManager();
            Bench = bench;
            CostList = costList;
            CostList.CButton.Click += (sender, args) => Craft();
            Update();
        }

        public bool OnAttemptAddItem(Item item)
        {
            if (Manager.AttemptAddItem(item))
                Update();
            else return false;

            return true;
        }

        public void Update()
        {
            Bench.UpdateBench(Manager.MainItem, Manager.Extras, Manager.GetResultDisplay(Bench.Context));

            UpdateCostList();
        }

        private void UpdateCostList()
        {
            Dictionary<Item, bool> costList = new Dictionary<Item, bool>();
            Inventory inventory = PlayerApplication.Instance.Player.PlayerInventory;
            if(Manager.CalculateCost() != null)
            Manager.CalculateCost().ForEach(item =>
            {
                bool valid = true;
                if (!inventory.Contains(item.Id))
                    valid = false;
                else if (item is CountableItem)
                    valid = ((CountableItem)item).Amount <= ((CountableItem)inventory.Get(item.Id)).Amount;
                costList.Add(item, valid);
            });

            CostList.Recycler.Adapter.SetItems(costList);
            CostList.CButton.Valid = costList.All(entry => entry.Value);
            CostList.CButton.Visibility = costList.Count == 0 ? ViewStates.Invisible : ViewStates.Visible;
            
        }


        private void Craft()
        {
            Item crafted = Manager.CalculateResult();
            if (CostList.CButton.Valid)
                EventManager.InvokeEvent(Events.ITEM_CRAFTED, new EA_ITEM_CRAFTED(crafted));
        }

        public void Reset()
        {
            Manager.Reset();
            Update();
        }
    }
}

The recipe manager:

using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using MineClick.Materials;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MineClick.Crafting.Recipes.Handlers
{
    internal class RecipeManager
    {
        public static readonly int EXTRAS_AMOUNT = 6;
        public Item MainItem { get; private set; }
        public Item[] Extras { get; private set; }
        IRecipeAcceptor RecipeAcceptor;

        public RecipeManager(Item mainItem, Item[] extras)
        {
            MainItem = mainItem;
            Extras = extras;
        }

        public RecipeManager() : this(null,new Item[EXTRAS_AMOUNT])
        {
            
        }


        public void SetRecipeItem(Item item)
        {
            if (item != null && RecipeRegistry.GetRecipeBindings().ContainsKey(item.Id))
            {
                MainItem = item.Clone();
                RecipeAcceptor = RecipeRegistry.GetRecipeBindings()[MainItem.Id];
            }
            else
            {
                MainItem = null;
                RecipeAcceptor = null;
            }

            Array.Clear(Extras, 0, Extras.Length);
        }

        public bool AttemptAddItem(Item item)
        {
            if (item == null)
                return false;
            bool success;
            if (MainItem == null)
                success = AttemptAddItemToMainSlot(item);
            else if (GetFirstEmptyExtraSlot() != -1)
                success = AttemptAddItemToExtra(item);
            else success = false;

            return success;

        }

        private bool AttemptAddItemToMainSlot(Item item)
        {
            if (!RecipeRegistry.GetRecipeBindings().ContainsKey(item.Id))
                return false;
            SetRecipeItem(item);
            return true;
        }

        private bool AttemptAddItemToExtra(Item item)
        {
            if (GetFirstEmptyExtraSlot() == -1)
                return false;
            if (!RecipeAcceptor.IsExtraFit(MainItem, item, GetPlainExtras()))
                return false;
            if (GetPlainExtras().Any(x => x.Id == item.Id))
                return false;

            Extras[GetFirstEmptyExtraSlot()] = item.Clone();
            return true;
        }

        public int GetFirstEmptyExtraSlot()
        {
            return Array.IndexOf(Extras, null);
        }

        public View GetResultDisplay(Context context)
        {
            
            if (RecipeAcceptor == null)
                return null;
            return RecipeAcceptor.DisplayResult(MainItem, GetPlainExtras(), context);
        }

        public Item CalculateResult()
        {
            if (RecipeAcceptor == null)
                return null;

            return RecipeAcceptor.CalculateResult(MainItem, GetPlainExtras());
        }

        public List<Item> CalculateCost()
        {
            if (RecipeAcceptor == null)
                return null;
            return RecipeAcceptor.CalculateCost(MainItem, GetPlainExtras());
        }
        
        /// <summary>
        /// Get extras not as slots array but as a plain list of extras (no null)
        /// </summary>
        /// <returns></returns>
        public Item[] GetPlainExtras()
        {
            return Extras.Where(x => x != null).ToArray();
        }

        public void Reset()
        {
            SetRecipeItem(null);
        }
    }
}

(I didnt add CraftingBench and CostListView since they only being used by the reciper manager and the error couldnt possibly be in them since both dont work,but let me know if any clarifaction needed)

Would really appreciate any help!


Solution

  • I have managed to solve the problem. As expected, it was on the CraftFragment and CraftAdapter: Im reseting the the adapter everytime the view is reset, and the BindListener of the event will still call the previous view. therefore, still showing input while debugging but not actually updating the current view. I temporarly solved it by creating only one instance of CraftAdapter in the fragment and updating its views every OnViewCreate.