Skip to content
Robin-Manuel Thiel edited this page Nov 27, 2015 · 9 revisions

How to create a module for IRMGARD?

General information

Modules in IRMGARD work encapsulated from the application structure so that they can be developed without a high amount of orientation within the project and are reusable for other projects. Each module provides the logic and UI for a specific Level Type so that it can be reused in every Level.

Architecture

Within the Android application modules are implented as Fragments. During runtime they will get loaded by the LessonFrameActivity regarding which type of lesson the user selected. These Fragments contain all the logic that is needed to work with the lesson. This includes all UI operations, Iteration management and the algorithms to determine whether a lesson has been solved correctly or not.

Each Level Type has its own model which holds the data that is needed by the according type of lesson. Typically this data contains a list of Iterations as well as information about the lesson's task. The available options are sometimes provided by the model or have to be automatically generated by the Fragment depending on the Level Type.

Implementing a new module

Model

The best way to start is creating a new model for the Level Type. For that create a class that inherits from Lesson. This class can be completely empty but needs to be created to identify it as a specific type of lesson later.

Afterwards options and iterations for the lesson have to be defined if they are needed. An iteration model inherits from the Iteration class and usually serves information for the Task, Options and other level type related properties.

If your module needs to define a class for the Options, you can do that, too. If your options are (or contains) letters, consider inheriting from the LetterBase class.

A model implementation for a new module can look like this (Find Missing Letter example):

namespace IRMGARD.Models
{
	public class FindMissingLetter : Lesson
	{
		public FindMissingLetter () {}

		public FindMissingLetter (int id, string title, string soundPath, string hint, LevelType typeOfLevel, List<Iteration> iterations)  : base (id, title, soundPath, hint, typeOfLevel, iterations)
		{
		}
	}

    public class FindMissingLetterIteration : Iteration
    {
        public List<TaskLetter> TaskLetters { get; set; }
        public List<FindMissingLetterOption> Options { get; set; }
        public bool HasLongAndShortLetters { get; set; }
        public bool RandomizeCase { get; set; }

        public FindMissingLetterIteration(List<string> lettersToLearn, List<TaskLetter> taskLetters, bool hasLongAndShortLetters, bool randomizeCase): base(lettersToLearn)
        {
            TaskLetters = taskLetters;
            HasLongAndShortLetters = hasLongAndShortLetters;
            RandomizeCase = randomizeCase;
        }
    }
        
    public class FindMissingLetterOption : LetterBase
	{
		public int CorrectPos { get; set; }

        public FindMissingLetterOption (string letter, bool isShort, bool isLong, int correctPos) : base (letter, isShort, isLong)
		{
			CorrectPos = correctPos;
		}
	}
}

UI

As a second step it is incidental to create a UI for the module. Within the Android application simply create a Layout file and add everything you need. Please consdider that your fragment will not have the full screen to display its lesson and make sure your UI meets the Design Guidelines.

In most cases, your module should provide a "Check" button where the user can check his results. Please include one in your layout and make sure it calls the CheckSolution() method that you will implement in the next step.

Fragment

Every lesson fragment needs to inherit from the abstact LessonFragment<T> class. T needs to be the model that has been created for the module in first step. It is also absolutely mandatory to overwrite the InitIteration() and CheckSolution() methods.

The InitIteration() method is called every time, an Iteration for this module has been changed. This also includes the first time, the fragment is loaded because every Lesson contains at least one Iteration which gets loaded as soon as the Lesson and its Fragment get loaded. Inside this method make sure to call base.InitIteration(); and do everything you need to do to prepare your Fragment afterwards. This contains logic and UI preparations, so make sure you create everything you need for your UI here. To access the current iteration, use the inherited GetCurrentIteration<T> method and provide the iteration class you expect as T.

As the name of the CheckSolution() method suggests it is responsible for checking whether the user inputs were wrong or right. The user will be able to click a "Check" button when he is done with the lesson to check his results. In this case the CheckSolution() method will be called to give him an appropriate feedback. When the result is calculated please call the inherited FinishIteration(bool) method to tell the system if the user has solved the lesson correctly or not.

Events

The communication between the fragment and the Activity that displays it is based on events. A LessonFragment offers a range of Events that the Activity can subscribe. Most of them are automatically fired by the LessonFragment<T> base class. One event that you have to fire by yourself is UserInteracted. You absolutely need to fire it as soon as the user makes his first "turn" on your UI. This enables the check button which lets the user checks his answers. Make sure that you calculate an isReady boolean before that you provide the the FireUserInteracted method. It indicates whether the user has filled out everything and his solution is ready to be checked. When the UserInteracted event gets fired and isReady is set to true, the "check button" will fly in.

An implemented Fragment could have a structure like this (Find Missing Letter example):

namespace IRMGARD
{
    public class FindMissingLetterFragment : LessonFragment<FindMissingLetter>
    {
        public FindMissingLetterFragment(Lesson lesson) : base(lesson) {}

        public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
        {
            // Prepare view
            // ...

            // Initialize iteration
            InitIteration();
            return view;
        }

        protected override void InitIteration()
        {
            base.InitIteration();

            var currentIteration = GetCurrentIteration<FindMissingLetterIteration>();
           
            // Create task
            // ...

            // Generate options
            // ...
        }

        private void Anything_ClickOrTouch(object sender, EventArgs e)
        {
            // Notify the Activity that the user has began interacting
            var isReady = true; // Usually a more complex calculation...
            FireUserInteracted(isReady);
        }       

        protected override void CheckSolution()
        {
            var isCorrect = false;
			
			// Check if user inputs have been correct
			// ...
           
            FinishIteration(isCorrect);           
        }
    }
}