30 December 2016

Improving on SimpleDateFormat


I'm a big fan of SimpleDateFormat, but it suffers from one critical problem. Your date format is forced on the user. I've learnt the hard way (as have most developers) that American's have their own date format (mm/dd/yyyy), us Brits have our own format (dd/mm/yy) and of course there are many other countries that also have different ideas.

Using SimpleDateFormat means you pick a format and the user has to like it. In some cases this could even be very frustrating for the user. You could of course allow them to pick their own format, but that's a lot of work.

My point here is Android has a little used method of doing this hard work for you and it utilizes the user's locale as set in the phone settings, to calculate this format. That is java.text.DateFormat.
So if the user has English American locale, then DateFormat will use that, if it has en-UK then that's the format it'll use. Magic!


import java.text.DateFormat;
DateFormat dateTimeFormat;


dateTimeFormat = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.SHORT, Locale.getDefault());


Date date = new Date();
dateTimeFormat.format(date);


Next time you're displaying a date or a time, why not try using DateFormat instead of forcing your format on the user?

22 December 2016

Inject Javascript into Android WebView

Here's an interesting one I stumbled across the other day. The ability to "inject" some javascript into a webview and override the existing javascript for a webpage.

Let's say you want to override an existing javascript function, maybe one that's broken or you just want to change functionality. This is possible using onPageFinished.

Now I'm not going to say you *should* do this, nor will I say it is recommended or a good idea. I'm just pointing out that it's possible and saying it's mildly interesting.
Obviously there are warnings that go with enabling javscript on your webview and you should take heed of them over my example here.

Here's my HTML that I will load in a webview. For this example I've loaded it locally from my assets folder. I see no reason why this wouldn't work on a remote page.


<html>
    <head>
        <title>hello</title>
        <script>
            function myFunction() {
                document.getElementById("demo").innerHTML = "Gonzo was here";
            }
        </script>
    </head>
    <body>
        <p>
            <button name="Sumit" label="Submit" value="Submit" id="Submit" onclick="myFunction()">Submit</button>

            <br /><br />
            <div id="demo">Hello</div>
        </p>
    </body>
</html>


Let's try and change that javascript function to do something else:


final WebView webView = (WebView) findViewById(R.id.webView);

webView.setWebViewClient(new WebViewClient() {
    @Override
    public void onPageFinished(WebView view, String url) {
        super.onPageFinished(view, url);
        webView.loadUrl("javascript:function myFunction(){document.getElementById(\"demo\").innerHTML = \"Paragraph changed.\";}");
    }
});

WebSettings webSettings = webView.getSettings();
webSettings.setJavaScriptEnabled(true);
webView.loadUrl("file:///android_asset/test.htm");


As you can see we've injected a custom function that overrides the results with a different output. Interesting huh?

13 November 2016

Android Round Launcher Icon


So the Google Pixel now allows you to use a round icon as your launcher icon. I've seen incredibly few apps implement this, even most Google apps have yet to update. That got me wondering how to achieve this new amazing (not at all copied from Apple) roundness?

The answer is this new tag in your manifest application:

<application
    android:icon="@mipmap/ic_launcher"
    android:roundIcon="@mipmap/ic_round_launcher"

round launcher icon

square launcher icon


Android is clever enough to apply this only to phones that support it and lets you
provide a normal boring square icon for old phones.

You are given this error in the IDE which is annoying:
  
    Resources referenced from the manifest cannot vary by configuration 
    (except for version qualifiers, e.g. -v21.) Found variation 
    in hdpi, mdpi, xhdpi, xxhdpi, xxxhdpi
  
It can safely be ignored or suppressed though.

25 October 2016

Google Pixel Review


Everyone and their dog seems to be putting out Google Pixel reviews so I thought I'd chip in with my thoughts and findings.

First thing I discovered is you need a nano sim. Grr, hadn't thought about that. Thankfully BT were amazing and I had one less than 24 hours later.

The phone is smaller than I expected, much smaller. I hate big phones and that's part of the reason why I've stuck with my Nexus 4 for four years. I was genuinely worried about it, but within a few hours I had gotten used to the Pixel's larger length and I have no regrets. It's incredibly small in depth and with is small too, so the larger height is well compensated for.

The phone itself is beautiful, the curves are nice and it feels very comfortable. The metal finish is a joy to feel and the screen blends well into the case. The back glass surrounding the camera and fingerprint reader is odd, I don't quite get it. It spoils the design a little and I don't know why it's not all metal, but it's not of any real consequence.

The screen is stunning, absolutely incredible, again I'm comparing most of this to my Nexus 4 so it is worlds apart. The colours and display are crystal clear and I sometimes find myself just staring at it.

The speed is lightening fast and it seems to cope with whatever I can throw at it with ease. I haven't really pushed it yet but it is so responsive and quick I can't see it struggling. The battery is good, compared to my four year old phone it lasts infinitely longer, but it's no more or less than I'd expect from a modern phone. It doesn't last weeks but it'll get me through a couple of days.

The USB C is a cool feature, but sadly it's not new nor unique, it works and it charges fast, actually it charges really really fast. Plus you're less likely to destroy your phone by ramming the charging cable in upside down. The fingerprint reader isn't new either, but frankly that's rocked my world! I love it.

Now the OS is a difficult one, it's fine and I have no complaints. However ...I am an Android developer and have used phones by every manufacturer you care to name and every Android OS extensively. I think we're well beyond the point where an OS update makes any real difference. In terms of speed and battery use, they've pretty much done all they can. What we see now is minor updates and UI tweaks like the settings changes. Not since Material design has anything really made much difference to the user. I'm not unimpressed, it just hasn't changed my interactions with the phone much at all.

There are a few things with confuse me and they are largely the things they've "borrowed" from Apple:

  • The round icons, not sure it makes a lot of difference, but what do we developers do? Can we release with round and square icons? If we switch to square, what happens to the old OSs that aren't prepared for round icons?
  • Quick Tap or whatever you call it where you can long press on a launcher icon? That's just a blatant rip off, and it adds nothing to the user experience. Bah!


Lastly is the Google Assistant, this is impressive! Its learnt my voice and ignores my girlfriend's, which I love! It understands easily what I'm asking it and responds quickly and generally with a surprising insight. That said....I'm still not going to talk to it!

So there are my highlights, in short it's a fantastically well put together phone and I'm really enjoying it. Go get one.

13 October 2016

Android exported provider


This is something simple, but I don't feel like it gets enough press:
https://developer.android.com/guide/topics/manifest/provider-element.html#exported

If you create a provider in your manifest and your minSdkVersion or targetSdkVersion is less than 17. The default value for exported is true! That means other applications on the device can access your provider! The safe way to always deal with this is always set exported="false".

03 September 2016

Android Favourite Star


So I recently needed a favourite button and I thought Android's classic star button would fit the bill quite nicely. So I found this stack overflow page which sounded like just what I needed:

http://stackoverflow.com/questions/8244252/star-button-in-android

I create my ImageView and set the src to @android:drawable/btn_star

However I came across one big problem, I couldn't for the life of me get the on state to stick. I tried setSelected but nothing happened. I looked at the drawable in the Android package and it seemed what I was really looking for was setChecked, but an ImageView doesn't have a checked state. Nor does a Button, nor a ImageButton and a CheckBox doesn't have a src. So I was stumped.

After some time I found what I needed

        <CheckBox
            android:id="@+id/item_star"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:contentDescription="@string/favourite"
            android:duplicateParentState="true"
            android:button="@android:drawable/btn_star" />


I used a checkbox with a button value set to the Android drawable. Hope it helps :)

This content is copyright and use limited to https://webdeveloperpadawan.blogspot.com/

30 May 2016

Android Lambda

I've heard a lot about lambda recently and how amazing Java 8 is. I'm not really sure this will have a huge impact on Android as I fail to see the relevance, none the less I was interested and keen to experiment.

I thought I'd create a super simple example by changing the OnClickListener of a button and trying to replace it with a lambda. Nothing shocking here, just wanted to know if I could. So I created a brand new project and here's how I got it working.

Before we go any further you'll need to:

  • Donwload the Android N SDK, 
  • Download JDK 1.8 (and target it with Android Studio) 
  • You'll need an emulator or device capable of running Android N.

Android N

To use lambdas we have to target Android N so I've updated the compile version, build tools, min Sdk and target Sdk.

android {
    compileSdkVersion 'android-N'
    buildToolsVersion '24.0.0-rc3'

    defaultConfig {
        applicationId "eightest.test.com.eighttest"
        minSdkVersion 'N'
        targetSdkVersion 'N'
        versionCode 1
        versionName "1.0"
    }

The code

This is what we would normally do for a onClick
findViewById(R.id.activity_main_text).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        showToast()
    }
});

This is how a lambda makes it look a little cleaner
findViewById(R.id.activity_main_text).setOnClickListener((View v) -> {showToast();});

Target Java 8

We need to specifically target java 8 (do this inside the Android brackets, after buildTypes)
    compileOptions {
        targetCompatibility 1.8
        sourceCompatibility 1.8
    }

Utilise Jack compiler

Now we need to tell Android to use the Jack compiler which will allow us to utilise Java 8.
    defaultConfig {
        applicationId "eightest.test.com.eighttest"
        minSdkVersion 'N'
        targetSdkVersion 'N'
        versionCode 1
        versionName "1.0"
        jackOptions {
            enabled true
        }
    }

This content is copyright and owned by https://webdeveloperpadawan.blogspot.com/

25 May 2016

Firebase Tutorial For Android Developers


I've sadly never had the chance to use Firebase in a production app. After Google talked about it so much at this year's IO I though it was about time I had a play and created a tutorial.

Firebase is primarily a real time database that makes data storage simple and easy. In the last few years it's been expanded to be an incredible fleet of tools that Android, iOS and Web developers can make use of.

The Firebase database is an inspired solution to data storage and syncing. The Firebase db is a NoSQL, cloud hosted database that allows for fast and easy sync across different devices. No more clumsy sync adapters or troublesome push notifications, Firebase handles all this for you with a simple API.

Also included in the suite is analytics, crash reporting, file storage, authentication, remote config and more.

I know I'm beginning to sound a bit like a marketing rep, but I really must say I'm flawed by how awesome Firebase is. The cloud sync database is really simple to set-up and it works incredibly well.

OK let's get into a tutorial. I wanted to link my app to Firebase and create a cloud db. Sounds easy! First you will be required to have a Google account, then we goto Firebase and create a new project:

https://console.firebase.google.com/

Now you have a Firebase app. Next we need to add it to Android, the wizard walks you through this process with a lovely little Material designed guide. You'll need the package name of your app and then to make some gradle and config changes.

Now we need to disable authentication. If you goto the Firebase console and click database on the left hand side you should see an empty data set. In the tabs above click on rules and change the json to match this:

{
  "rules": {
    ".read": true,
    ".write": true
  }
}

This basically disables all authentication giving anyone write and read access. For a real application you'd want to set-up proper user role based access, but for this tutorial will just turn authentication off.

Next you'll need to setup your Android project. You should already have the json config file and the gradle additions. Now you need to add the database gradle dependancy:

compile 'com.google.firebase:firebase-database:9.0.0'
That's it, you're pretty much ready to go. However there are a few important db constraints to get your head around before you launch your new app.

Data in Firebase is all JSON but it does support saving objects, lists, maps as well as simple data types such as strings, booleans, doubles etc. It seems to me largely based around a key value pair type of system. You put a value with it's corresponding key to Firebase and it syncs it. Then use the same key to retrieve that data.

SetValue

SetValue is an assignment method. At first I thought you could use it to create new db entries, but that is not the case. Instead you call

FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference myRef = database.getReference("message");

myRef.setValue("Hello, World!");

This creates a key value pair with the key "message" and the value "Hello, World!". If you call setValue again on that key, the value is overwritten, a new pair is not created in this way.

ValueEventListener

The ValueEventListener gives you the ability to receive a callback when your data changes. This is useful when you create your UI so you can receive updates whenever the data changes. No more db listeners or broadcasts.

public void onDataChange(DataSnapshot dataSnapshot)

All you need to do is attatch the listener

myRef.addValueEventListener(this);

Push

Push allows create a list of data. Using this method generates a unique key instead of forcing you to create a new key for every object.

Save Data

Here's how I added a list of simple objects. Player is just a simple pojo. We use push to create a new child of the DB_KEY_PLAYERS type. We then get the key of that new child.
Using that key we can now update it's data. The second param to setValue is a callback so we can update the UI when the data has been updated successfully.

FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference myplayers = database.getReference(DB_KEY_PLAYERS);

//Get key for a new player
String key = myplayers.push().getKey();
Player player = new Player(playername);
//set value of new player
myplayers.child(key).setValue(player, ActivityAddPlayer.this);


That's it, you should now have a working cloud synch'd database. You can now add analytics and crash reporting as well, super simple and very well designed. I really hope I get to put it into practice in a live app sometime soon.

This content is copyright and owned by https://webdeveloperpadawan.blogspot.com/

16 May 2016

Android - There is a problem parsing the package

This was a head scratching problem I had. I copied an apk to my phone and tried to install it but immediately got an error

There is a problem parsing the package

So I opened up Android Studio, assembled it again and re-installed it. Same problem. I checked the manifest, checked gradle and a few million other things! Some hours later I tried:

adb install C:/folder/myapp.apk

Then I checked the ADB logcat and finally I had something to go on.

Installation error: INSTALL_PARSE_FAILED_MANIFEST_MALFORMED

How frustrating! Couldn't I have been told that during build? Well at least I had something. It turned out to be a missing provider authority for a specific flavour. Oh well, next time I'll know what to do first!

03 May 2016

Android n00b lessons

I've been training a couple of relatively junior Android developers recently and I've seen a few mistakes repeated. I thoughts I'd mention them here in the hope it will help someone else. This isn't supposed to be a laugh at anyone's expense, just a discussion about how to improve your code and become a better developer.
  1. Don't close a cursor properly
    I see this in on-line examples, in old code and in new code written by juniors. The fact is there are a lot of things that can go wrong when using a cursor, so you need to be a bit careful and make sure you don't throw an error or waste memory. 
    1. First and foremost close the cursor.
    2. Your cursor might be null
    3. Use finally to close your cursor, it works really well!

    Cursor data = context.getContentResolver().query(MyProvider.DETAILS, null, null, null, null);
    
    try {
        if (data != null && data.moveToFirst()) {
            retVal = data.getString(data.getColumnIndex(columnName));
        }
    } finally {
        if(data != null) {
            data.close();
        }
    }

  2. Catching an error badly
    Arrrg! I see this far too often. An empty catch block. Even if it's just a Log.e, that's better than nothing. OK I'll admit there are some situations where you just don't care if an error is thrown (like above), but all too often people just throw the try in to avoid compile errors and leave the catch empty. Don't do it!

            try {
                int a = 1;
            }catch(Exception e){
                    
            }
    

  3. Excessive use of RecyclerView
    RecyclerView is new, it's cool and it's heavily publicized. That doesn't mean you should use it ALL the time. If you've got a small simple app with one ListView that will show about three elements, please don't bring an entire new library into the project. A ListView is OK. If your ListView is small and your needs simple, don't panic, you can use a ListView. The world will not end, I promise. Yes the RecyclerView is efficient, especially when you want to use animations or change elements but sometimes it's like cutting the grass with a machine gun. I don't want to say RecyclerView is bad, it's a fantastic tool, but use your perspective and let's keep it simple people!

  4. Variables in a loop
    This one was really interesting. Conventional wisdom often states you should never create a variable in a loop, and to be honest I always held with this. However I did some research recently and it seems that it's actually most efficient to declare the variable in the smallest scope possible. If that means declaring it in the loop, then fine, as long as that's NOT then used outside the loop.

    for (Person person : people) {
        String desc = person.getDescription();
        ...
    }
    

If you've any more suggestions then I'd love to hear them.

16 March 2016

Android social media - login with Facebook


I recently thought I’d try and add Facebook and Twitter login to an Android app I was creating. This is never as straightforward as it sounds as you've got to fully integrate with each parties SDK and link your app to their systems. First I'm going to tackle Facebook.


Facebook


Let’s try and add a Login With Facebook button to an app.


1) Sign Up as a Developer

First you need to sign-up on Facebook as a developer. You’ll need an existing Facebook account, but once you've done that you just sign up as a developer here:


2) Create an app.

This is your Facebook SDK “app” (not your Android app). This allows you to link your Android app with Facebook. It also allows Facebook to monitor your app and provide you with usage statistics. To get going go to this page and follow the steps.

3) Create your Android app 

Now we change to Android studio, create a new app, or open your existing app, whichever works for you. We need to tweak this app a little:
  • Add Maven Central to your gradle file
  • Add a dependency on Facebook SDK
  • Add a string to your strings file with your facebook_app_id
  • Add Facebook meta data to your manifest
  • Don’t forget to make sure you have Internet permissions
To be honest the Facebook quick start guide explains all the above really well with copy and paste code. Just use their example.

4) Configure your Facebook App

Now you need to configure the Facebook App to match your Android app. 
This involves finding your key hash. This is a bit complicated and can be quite confusing. The easiest way I know to do this is follow instructions on this Stack Overflow answer:
This means you don’t have to download, install and configure open ssl and other such tools, which is a relief. 

That’s it, if all this works you should be setup to start using Facebook login on your app. All that’s left now is to add the Android client code.


Android


Now we can add code to our Android app to show a Facebook Login button and to respond to Facebook login events. Thankfully this is relatively simple compared to the above.

1) Add an Application class

Create a java class that is similar to your app name and make it extend Application. Then add FacebookSdk.sdkInitialize

public class SocialMedia extends Application {
    @Override
    public void onCreate() {
        super.onCreate();

        FacebookSdk.sdkInitialize(getApplicationContext());
    }
}

Then add the name of this file to the application tag in your manifest:
android:name="SocialMedia"

This makes Android treat the SocialMedia file as a bit of an OnApplicationStart type file.


2) Add XML to layout


First add the xml for the button to your layout file where you want the user to see the big blue button.

<com.facebook.login.widget.LoginButton
  android:id="@+id/facebook_login_button"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:layout_gravity="center_horizontal"
  android:layout_centerHorizontal="true"
  android:layout_marginTop="30dp"
  android:layout_marginBottom="30dp" />

3) Add Java code to Activity

Now open up that view’s corresponding activity and add the following code:

a) Add the following two member variables at the top of the file:

LoginButton mFbLoginButton;
CallbackManager mCallbackManager;

b) Initialize these variables in onCreate

mCallbackManager = CallbackManager.Factory.create();

//Facebook button
mFbLoginButton = (LoginButton) findViewById(R.id.facebook_login_button);
mFbLoginButton.setReadPermissions("user_friends");

c) Register Callbacks for button clicks

mFbLoginButton.registerCallback(mCallbackManager, new FacebookCallback<LoginResult>() {
  @Override
  public void onSuccess(LoginResult loginResult) {
     Log.i("socialmedia", "onSuccess");
     Toast.makeText(MainActivity.this, "Success", Toast.LENGTH_SHORT).show();
     String fbToken = AccessToken.getCurrentAccessToken().getToken();
     Log.i("socialmedia", "fbToken:" + fbToken);
  }

  @Override
  public void onCancel() {
     Log.i("socialmedia", "FB onCancel");
     Toast.makeText(MainActivity.this, "FB onCancel", Toast.LENGTH_SHORT).show();
  }

  @Override
  public void onError(FacebookException exception) {
     Log.i("socialmedia", "FB onError");
     Toast.makeText(MainActivity.this, "FB onError", Toast.LENGTH_SHORT).show();
  }
});

d) Add an onActivityResult

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  super.onActivityResult(requestCode, resultCode, data);
  mCallbackManager.onActivityResult(requestCode, resultCode, data);
}

So hopefully that is all fairly self explanatory. The Login button opens a new Facebook window to confirm the user wants to share their details. mCallbackManager is responsible for receiving the result of that window and passing it to your callback. From there you can do what you want, copy the token and close the activity or whatever.

Hope it helps.

25 January 2016

Using a mock RESTful API with Android OkHttp and getSandbox

I recently stumbled across a really cool service called getSandbox.com. This is a API mocking service which can mimic RESTful or SOAP webservices. This is incredibly useful for knocking up quick testing tools or as an endpoint for unit tests.


This has huge advantages as it allows you to protect your server from unit test bombarding, however I'm more interested in just being able to use it just to quickly setup a few examples for prototyping. You can deploy it on your server or using their cloud, but they offer a free tier just for a low number of requests.


So I decided to setup a getsandbox account and use it with okHttp on Android to see if I can do some simple REST get and post requests.




First I created a new sandbox, I didn't have an Apiary account and this is a test API so I've no WSDL nor a RAML and I don’t know what Swagger is. So I picked blank. This is great, it gives you a temporary one hour sandbox with a few simple get and post methods.


  • GET/hello - Respond hello world
  • GET/users - Show all users
  • POST/users - Add new user


Sandbox even gives you some form of storage, so anything you add is persistent.


With my new endpoints I created a form (see below) to test the POST and in a browser hit the main url to get all users. Magic, everything is responding wonderfully. Sandbox is really that simple and easy to use! I'm super impressed.


Next we’re over to Android and I wanted to use OkHttp for this one. OkHttp is a neat little library that handles http requests for you and easily deals with any problems. Plus it can make synchronous and asynchronous calls with ease.

I created a new Android Studio project and added in the okhttp gradle compile:

compile 'com.squareup.okhttp3:okhttp:3.0.1'

Then I put INTERNET permissions in the manifest, a ListView in my activity view and started the java code. First I made my activity implement okhttp3.Callback, most tutorials will tell you to put the callback inside the method call, but personal preference, I like implements.
Here’s the simple OkHttp code:

OkHttpClient client = new OkHttpClient();
//Create a simple get request
Request request = new Request.Builder().url(sUrl).build();
//Execute with call back
client.newCall(request).enqueue(this);

You don’t get much simpler than that. The request is a fairly self evident GET request, nothing in the body and nothing fancy.
Next you have a choice,


client.newCall(...).execute or .enqueue.


Execute is a blocking synchronous call whilst enqueue is an asynchronous call. We’ll use enqueue as we can’t do a blocking call on the UI thread anyway.

So my Activity now overrides onFailure onResponse

@Override
public void onResponse(Call call, Response response) throws IOException {
  Log.i("http", "onResponse");

  int j = 0;
  String[] users = null;

  try {
     //Read string in as json array
     JSONArray jsonArray = new JSONArray(response.body().string());

     users = new String[jsonArray.length()];
     //add each element in array to string array
     for (int i = 0; i < jsonArray.length(); i++) {
        if(jsonArray.get(i) != null) {
           users[j] = jsonArray.get(i).toString();
           j++;
        }
     }
  } catch (JSONException e) {
     e.printStackTrace();
  }

  if (users != null){
     //Create adapter of users
     mAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, users);

     //listview needs to be updated on ui thread.
     runOnUiThread(new Runnable() {
        @Override
        public void run() {
           mListView.setAdapter(mAdapter);
        }
     });
  }
}


That's pretty much it. The code in onResponse isn't rocket science. We take the response from OkHttp and parse it as a json array. We can now loop through it and create a normal String array. We could do a bit more error checking here and clean some dodgy input, but for sake of speed that's your lot. The only slight oddity is having to set the listview using runOnUi because we can't change the UI elements outside of the UI thread. The again....now I think, I didn't really need to set that after the data did I? I could have done that in the Acivity onCreate....oh well, next time!

So look what we've done in just a few lines of code and a few clicks. We've done some web interaction without any need for asyncs or checking if we have a connection. Plus we've got a fantastic test API which responds quickly, updates instantly and is super easy to use.


Just in case you need to submit to a POST, here's my mega javascript form:

<!DOCTYPE html>
<html>
    <head>
        <script type="text/javascript" src="https://code.jquery.com/jquery-2.2.0.min.js"></script>
        <script type="text/javascript">
            $(document).ready(function() {
                $('#submit').click(function(){
                
                    console.log('hiii');

                    $.ajax({
                        url: "http://**myid**.getsandbox.com/users",
                        type:'POST',
                        data:
                        {
                            username: $('#username').val()
                        },
                        success: function(msg){
                            $('#username').val("");
                        }               
                    });

                    return false;
                });
            });
        </script>
    </head>

    <body>
        <form>
            <input type="text" name="username" id="username" value="" />
            <button name="go" id="submit">Submit</button>
        </form>
    </body>
</html>