Apr 17
The Jedi Mindtrick for Learning Quickly
Welcome young Paduan! I see great potential in you. Use the force, you will, and become great and powerful! Guide you, I will, in this new way until you have mastered - The Jedi Mind Trick... The "Jedi Mind Trick" is a method for learning anything new. For software guys and gals like us, being able to learn is one of our key skills which is why I think this method is potential dynamite - almost a super power we can use.
(Shoutout to Chris Chiesa - the speed typing demon - who asked how I learn new tech. Here you go Chris - all my secrets laid bare! :-)) Some History I've been interested in productivity for years. As part of my quest to become more productive, my friend Gk and I
...interviewed people we considered as "highly productive" programmers in an attempt to discover what sets them apart. One programmer whom we interviewed is the completely amazing owner of India's most successful accounting package - Tally. In India, Tally is ubiquitous. You see Tally in small and dingy warehouses, in brightly lit, expensive high-end shops, in mom-and-pop stores, and I've even seen it once in a police station! And BG, the owner of Tally solutions, was for many years also the sole developer, tester, and bug-fixer. He is an incredibly gifted and productive programmer.
So, of course, we interviewed him as part of our research. "How do you learn new things?" is one of the standard questions we ask in our interviews. We've got some good responses to this one including the importance of blocking time off and working on the fundamentals. BG's answer, however, was pretty unexpected and took me by surprise. "Whenever I'm learning something new," he said, "I start by trying to guess how it will work and then see how well my guess pans out." "For example," he continued, "When C++ introduced exceptions I was excited to try them out. But I started by trying to guess what the syntax of a C++ exception would be before going to look at the new standard." I nodded calmly, not wanting to break the interview flow, but inside I was in turmoil. WHY WOULD YOU DO THAT? I wanted to ask, IT'S A WASTE OF TIME! IT'S SILLY TO BE GUESSING WHEN YOU CAN'T POSSIBLY KNOW! But instead, I waited for the standard two second beat to let the interview subject complete the thought if he had more to say and moved on to the next question. Part of our interview rules was never to challenge anything a subject said in any way. This was because our subjects found it hard enough to talk about their practices and habits and even small challenges caused some of them to shut down and stop sharing anything they could not adequately explain or defend. Another rule was not to discard anything - even if it seemed counterintuitive - without trying it out. So, silly as I found it, I tried BG's method out...
...and it worked! And it worked brilliantly too! I found learning to be more pleasant, fun, and effective using this method.
But...but why does it work? Well after using it for a while here's my best guess. Why it Works Here's what happens. There are basically three ways to learn: 1. By dint of repetition
(This is boring and not very effective)
2. By heightened emotion
(This is HIGHLY effective but sometimes difficult to set up in a learning environment)
3. By association with what we already know.
(This is reasonably effective and quite fun!)
The association method needs a bit more explaination and the Baker/Baker paradox illustrates it perfectly. The way it goes is this. A researcher shows two people the same photograph of a face and tells one of them that the guy is a baker and the other that his last name is Baker. A couple of days later, the researcher shows the same two subjects the same photograph and asks for the accompanying word. The person who was told the man worked as a baker is much more likely to remember it than the person who was given his surname. Why should that be? Same photograph. Same word. Different amount of remembering. This is because the profession "baker" has a strong net of associations already in our memories. Hence we can remember it far more simply and easily.
So now the idea of "guessing" makes more sense. Making a guess forces us to form associations with things we already know. Because we do this, our learning moves from needing repetition to association which is far more powerful. Another side-effect of "guessing" is it forces us to recall what we already think we know and helps solidify our understanding (Active Recall).
What about emotion? Well a simple way to incorporate emotion in learning is to pick an outcome that we are invested in and truly care about that the learning with give us. How do we do this? By applying the learning immediatly into a goal we truly desire. The goal itself must be important to us - not a pretend-goal to 'trick' us into learning but a goal so strong that it stands independent of learning.
. So the entire "Jedi Mind Trick" for learning goes as follows: 1. Pick a goal we really care about. This must be something we really want and that we think can be accomplished by learning whatever we have taken up. 2. Make a guess as to how to proceed (and be confident about it like a Jedi). 3. Read/experiment and either confirm or correct our guess. We should read enough to feel confident of our understanding and not just enough to "correct" any mistakes. .
To show you how it works, let us walk through an example together. First, let's pick something useful to learn. Because I've just bought my first Android phone, I think Android programming would be a good subject. Now I have done iOS programming but I've never touched Android. I have no idea how it works besides a vague notion it uses Java. So this seems like a good test for us to walk together.
Step 1: Set a Goal
goal.png:I want an andriod app similar to the excellent "off the mind" where I can quickly drop in notes. I couldn't find a similar note taking app on the Android Playstore so I really need to make one. And it's ideal to build because I don't want an app with lots of options, I just need to single-touch, type, forget. (Using the "Jedi Mind Trick" here is the app I built in half a day going from ZERO knowledge of Android to having a useful app on the Play Store:
You can download and use the app by clicking on the following link:
So now follow along as we build the app by using the "Jedi Mind Trick" of simply making predictions and checking them.) Step 2: Make a Jedi Prediction (otherwise known as a guess!) Step 3: Confirm or Correct (and repeat!)
prediction.png:Android is a java platform so I should be able to use Eclipse. I will need some emulators.
prediction-wrong.png: The best way to develop an andriod app is to use "the andriod studio". Apparently it organizes projects better and is simpler for beginners. Plus Android has officially discontinued support for Eclipse. So I'm going ahead and installing Android Studio.
prediction.png: Android studio will give me a skeleton template to create an app.
prediction-right.png: Yup. Create a new Android Project is the first thing on the list. Let's go!
prediction.png: Will create a project with lots of files and a skeleton app that will show "hello world".
prediction-wrong.png: Asking me to choose something called "Activity". Not sure what that is so let's guess.
prediction.png: "Activity" is a set of widgets and handlers to perform some basic user interaction behaviours. Selecting "Empty activity" and going ahead.
prediction-right.png: The next page seems to say that an "Activity" is a class with a layout which I assume contains the widgets.
prediction-right.png: Hah! Got a "Hello world!" :-)
prediction.png: The class will contain methods that hook into the layout and respond to user actions.
prediction-wrong.png: The class does not seem to contain hook methods for the layout. It contains one lifecycle method (onCreate). Looking at all the methods it could override shows that it hooks into the lifecycle of the application (onPause,onResume,onLowMemory, etc). It also appears to contain hooks for the generic andriod interface actions (keydown, keyup, touch...) and high level UI elements (options menu, context menu, toolbar,...)
(Moving this text from "learn.txt" to the MainActivity.java file so that it can be published on my blog as working code).
prediction.png: I will be able to find a simple text entry widget and replace the "Hello World" with it.
prediction-right.png: Yup
prediction.png: I can add a button to the top toolbar and respond when the user clicks it.
prediction-wrong.png: After trying to add a button for a while and drag it "nicely" to the toolbar it just doesn't work. The top bar is not a "toolbar" but an "actionbar". Also according to the documentation, we should not be using the action bar because it behaves differently across releases of Android. Instead we should remove the action bar and replace it with a library "toolbar" which will behave the same across releases. Okie-dokie.
prediction.png: I can add buttons to the "toolbar" that I couldn't to the "actionbar".
prediction-wrong.png: Nope! I still can't. Turns out that I need to add "actions" to the toolbar which are menu items.
Whoops! The toolbar is floating on top of the edit text box! The first few lines of the text box are hidden under the toolbar.
prediction.png: There will be a simple layout that can stack one component and then the other
prediction-wrong.png: It turns out that the simplest way is simply to add a padding to the top of the editbox as the toolbar is fixed in size.
prediction.png: The control type is "Multi line Text" but in order to access it I will need to know it's class. I see a "EditText" which looks hopeful so let's try that.
prediction-right.png: Yup it works:
Now I need a way to store this so we don't lose it when the app quits.
prediction.png: The way to save data is to open a file and write to it.
prediction-wrong.png: There are actually three ways to save data: 1. Key-value properties 2. File (- as we guessed -) 3. SQL Lite database. We'll go ahead and continue saving our notes in a file.
prediction.png: There will be a standard location we can use to store our files.
prediction-right.png: Yup. There's a `getFilesDir()` method that gives us the location we need.
prediction.png: We can save and read the file data easily.
prediction-right.png: Yup. Using input and output streams with simple code samples.
Whenever I save now, it appends to the file. So I need to do two things: 1. Clear the text in the input box after each save. 2. Put a "header" between each save to differentiate notes. I also need a simple way to clear all notes. For this, I will add a "delete" button.
prediction.png: There will be a simple way to delete files by name.
prediction-right.png: File.delete(). :-D (As all this code is similar to what we've done before I'm not going to show it here but you can see it in the actual code below)
Now we make the app really useful! How? Well, for starters, it should be ready to type with a single click! So we need to bring up the keyboard on start and save and clear whenever we stop.
prediction.png: To bring up the keyboard I can set focus to the EditText on start.
prediction-wrong.png: I can bring up the keyboard when an activity is launched. After reading a bit more on "Activity" this makes sense - we should be able to start the user interface with the keyboard if needed.
prediction.png: There will be an event I can override for stopping the app.
prediction-right.png: Yup. It's called onPause() Moving the code to onPause()... When I use the app now, I have to save notes by dismissing the app. I'd like to do this within the app rather than going "out" to the Home/_Back_ button.
prediction.png: I can add a "back" action to the toolbar and position it to the left.
prediction-wrong.png: Nope we can't add actions to the left. Instead the toolbar is structured and has these components: (a) Navigation (b) Icon (c) Title/Subtitle (d) Actions/Menu (e) Custom views So what we are looking for is to enable the "navigation" and use it to dismiss the application. This will trigger the onPause and save our note.
prediction.png: There is a simple way to dismiss an application.
prediction-right.png: Yes, we simply need to close all current dialogs etc and move the task to the back of the activity stack.
One issue we have so far is the "delete" button just deletes. We should ask the user if they actually want to delete or they touched the button by mistake. Ideally we should provide an "UNDO" option but I can't figure out how the UI for that would look so let's go with a simple confirmation for now.
prediction.png: There should be some sort of simple MessageBox class I can use.
prediction-wrong.png: Apparently not so simple. I have to create a "DialogFragment" and use that. This is because we can use fragments to manage sub-views with their own lifecycles. Dialog is a 'standard' sub-view but it's been generalized to a fragment.
prediction.png: The fragment itself will handle the confirm/ignore buttons so we make it an inner class.
prediction-right.png: Yup.
Now all that's left is to send an email when the email button is clicked!
prediction.png: There must be a class that helps us send emails.
prediction-wrong.png: The better way is to register an 'intent' and allow the user to use his or her existing client. (Also discovered a nice UI element called "Toast" to show brief feedback messages)
And finally, let's create some icons and we're done with the application!
Now the last thing to do is publish it on the playstore.
prediction.png: It will be easy and free to publish the app on the playstore.
prediction-wrong.png: Awp!! I have to pay to get my free app shared! Sigh. Otherwise it was easy enough! It's available here:
Zero to Hero! Well - from Android Zero to Hero in half a day! All because I can use the Jedi Mind Trick! :-) Let me know how it works out for you and if you have any doubts in the comments.
Addendum: After uploading this to the app store and trying it out I have made a few more minor changes: 1. Renamed the main class to JediMindTrick. 2. Added a "number of notes" indicator. 3. Added a "Preview" to see the notes saved so far. 4. Added the ability to accept data from other applications. Because both these are trivial changes I am not updating this blog post. However the older code can be found in the github repository history.
package blog.theproductiveprogrammer.notegrabber; import android.app.Dialog; import android.content.ClipData; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.graphics.Color; import android.net.Uri; import android.support.v4.app.DialogFragment; import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.support.v7.widget.Toolbar; import android.view.Gravity; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.EditText; import android.widget.Toast; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.InputStreamReader; import java.text.SimpleDateFormat; import java.util.Date; public class JediMindTrick extends AppCompatActivity { private static final String DIVIDER = "=================="; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Toolbar myToolbar = (Toolbar) findViewById(R.id.my_toolbar); setSupportActionBar(myToolbar); myToolbar.setSubtitleTextColor(Color.WHITE); myToolbar.setNavigationOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { finish(); moveTaskToBack(true); } }); setNumberOfNotes(); handleIntent(); } /** * [!] We accept data * directly from other apps. * [+] Check if there is * a "text/plain" type, we * use the intent data. */ private void handleIntent() { Intent intent = getIntent(); if(intent == null) return; String type = intent.getType(); if(type != null && type.equals("text/plain")) { String data = intent.getStringExtra(Intent.EXTRA_TEXT); if(data != null) { EditText edittxt = (EditText) findViewById(R.id.editText); edittxt.setText(data); } } } private void setNumberOfNotes() { Toolbar myToolbar = (Toolbar) findViewById(R.id.my_toolbar); int n = getNumberOfNotes(); if(n == 0) myToolbar.setSubtitle("(no notes)"); else if(n == 1) myToolbar.setSubtitle("(1 note)"); else myToolbar.setSubtitle(String.format("(%d notes)", n)); } private int getNumberOfNotes() { /* read the file line-by-line and count the number of dividers "=====..." */ int num = 0; try { FileInputStream is = openFileInput(filename); BufferedReader reader = new BufferedReader(new InputStreamReader(is)); StringBuilder sb = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { if(line.equals(DIVIDER)) num++; } reader.close(); } catch (Exception e) { e.printStackTrace(); } return num; } @Override protected void onPause() { super.onPause(); dumpText(); } @Override protected void onResume() { super.onResume(); setNumberOfNotes(); } @Override public boolean onOptionsItemSelected(MenuItem item) { if(item.getItemId() == R.id.exportEmail) { dumpText(); return export_notes_in_email_1(); } if(item.getItemId() == R.id.deleteNotes) { dumpText(); deleteSavedNotes(); return true; } if(item.getItemId() == R.id.previewNotes) { dumpText(); return previewNotes_1(); } return super.onOptionsItemSelected(item); } private boolean previewNotes_1() { Intent intent = new Intent(this, PreviewNotes.class); intent.putExtra(Intent.EXTRA_TEXT, loadText()); startActivity(intent); return true; } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main_actions, menu); return true; } String filename = "jedimind"; private String loadText() { FileInputStream inputStream; try { inputStream = openFileInput(filename); return to_string_1(inputStream); } catch (Exception e) { //uncomment to debug: e.printStackTrace(); } return ""; } private static String to_string_1(FileInputStream is) throws Exception { BufferedReader reader = new BufferedReader(new InputStreamReader(is)); StringBuilder sb = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { sb.append(line).append("\n"); } reader.close(); return sb.toString(); } private void saveText() { FileOutputStream outputStream; String usertxt = get_user_text_1(); if(usertxt == null) return; try { outputStream = openFileOutput(filename, Context.MODE_APPEND); outputStream.write(getCurrentTimeStamp().getBytes()); outputStream.write(("\n"+DIVIDER+"\n").getBytes()); outputStream.write(usertxt.getBytes()); outputStream.write("\n\n\n\n".getBytes()); outputStream.close(); } catch (Exception e) { e.printStackTrace(); } } private String get_user_text_1() { EditText user_text = (EditText) findViewById(R.id.editText); String usertxt = user_text.getText().toString(); if(usertxt == null) return null; usertxt = usertxt.trim(); if(usertxt.length() == 0) return null; return usertxt; } private void dumpText() { saveText(); clear_text_1(); setNumberOfNotes(); } private void clear_text_1() { EditText user_text = (EditText) findViewById(R.id.editText); user_text.getText().clear(); } public void deleteNotes() { new File(getFilesDir(), filename).delete(); setNumberOfNotes(); Toast toast = Toast.makeText(this, "Notes Cleared", Toast.LENGTH_SHORT); toast.setGravity(Gravity.CENTER, 0, 0); toast.show(); } public static class ConfirmDelete extends DialogFragment { private JediMindTrick parent; @Override public void onAttach(Context context) { super.onAttach(context); parent = (JediMindTrick) context; } @Override public Dialog onCreateDialog(Bundle savedInstanceState) { // Use the Builder class for convenient dialog construction AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); builder.setMessage("Clear all notes?") .setPositiveButton("Clear", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { parent.deleteNotes(); } }) .setNegativeButton("No", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { // Do nothing - User cancelled the dialog } }); // Create the AlertDialog object and return it return builder.create(); } } private void deleteSavedNotes() { DialogFragment dialogFragment = new ConfirmDelete(); dialogFragment.show(getSupportFragmentManager(), "confirmdialog_tag"); } private String getCurrentTimeStamp() { SimpleDateFormat format = new SimpleDateFormat("MMM dd, yyyy HH:mm"); return format.format(new Date()); } private boolean export_notes_in_email_1() { String subject = "Note Grabber (" + getCurrentTimeStamp() + ")"; String body = loadText(); Intent i = new Intent(Intent.ACTION_SEND); i.setType("message/rfc822"); i.putExtra(Intent.EXTRA_SUBJECT, subject); i.putExtra(Intent.EXTRA_TEXT, body); try { startActivity(Intent.createChooser(i, "Send mail...")); return true; } catch (android.content.ActivityNotFoundException ex) { Toast.makeText(this, "There are no email clients installed.", Toast.LENGTH_SHORT).show(); return false; } } }
. . . . . . . . . .
Notify me on new blog posts
. . . . . . . . . .