\n\u003Cuses-permission android:name=\"com.synyx.cloudmessagetest.permission.C2D_MESSAGE\"/>\n \u003C!-- App receives GCM messages. -->\n\u003Cuses-permission android:name=\"com.google.android.c2dm.permission.RECEIVE\"/>\n \u003C!-- GCM connects to Google Services. -->\n\u003Cuses-permission android:name=\"android.permission.INTERNET\"/>\n \u003C!-- GCM requires a Google account. -->\n\u003Cuses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>\n \u003C!-- Keeps the processor from sleeping when a message is received. -->\n\u003Cuses-permission android:name=\"android.permission.WAKE_LOCK\"/>\n",[45,8278,8279,8283,8288,8293,8298,8303,8308,8313,8318,8323,8328,8333,8338],{"__ignoreMap":43},[48,8280,8281],{"class":50,"line":51},[48,8282,178],{"emptyLinePlaceholder":177},[48,8284,8285],{"class":50,"line":57},[48,8286,8287],{},"\u003Cpermission\n",[48,8289,8290],{"class":50,"line":63},[48,8291,8292],{}," android:name=\"com.synyx.cloudmessagetest.permission.C2D_MESSAGE\"\n",[48,8294,8295],{"class":50,"line":69},[48,8296,8297],{}," android:protectionLevel=\"signature\"/>\n",[48,8299,8300],{"class":50,"line":75},[48,8301,8302],{},"\u003Cuses-permission android:name=\"com.synyx.cloudmessagetest.permission.C2D_MESSAGE\"/>\n",[48,8304,8305],{"class":50,"line":81},[48,8306,8307],{}," \u003C!-- App receives GCM messages. -->\n",[48,8309,8310],{"class":50,"line":124},[48,8311,8312],{},"\u003Cuses-permission android:name=\"com.google.android.c2dm.permission.RECEIVE\"/>\n",[48,8314,8315],{"class":50,"line":129},[48,8316,8317],{}," \u003C!-- GCM connects to Google Services. -->\n",[48,8319,8320],{"class":50,"line":204},[48,8321,8322],{},"\u003Cuses-permission android:name=\"android.permission.INTERNET\"/>\n",[48,8324,8325],{"class":50,"line":210},[48,8326,8327],{}," \u003C!-- GCM requires a Google account. -->\n",[48,8329,8330],{"class":50,"line":216},[48,8331,8332],{},"\u003Cuses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>\n",[48,8334,8335],{"class":50,"line":357},[48,8336,8337],{}," \u003C!-- Keeps the processor from sleeping when a message is received. -->\n",[48,8339,8340],{"class":50,"line":363},[48,8341,8342],{},"\u003Cuses-permission android:name=\"android.permission.WAKE_LOCK\"/>\n",[18,8344,8345],{},"And declare a GCM broadcast receiver within the application tag:",[38,8347,8349],{"className":3414,"code":8348,"language":3416,"meta":43,"style":43},"\n\u003Creceiver\n android:name=\"com.google.android.gcm.GCMBroadcastReceiver\"\n android:permission=\"com.google.android.c2dm.permission.SEND\">\n \u003Cintent-filter>\n \u003Caction android:name=\"com.google.android.c2dm.intent.RECEIVE\"/>\n \u003Caction android:name=\"com.google.android.c2dm.intent.REGISTRATION\"/>\n \u003Ccategory android:name=\"com.synyx.cloudmessagetest\"/>\n \u003C/intent-filter>\n\u003C/receiver>\n\u003Cservice android:name=\".GCMIntentService\"/>\n",[45,8350,8351,8355,8360,8365,8370,8375,8380,8385,8390,8395,8400],{"__ignoreMap":43},[48,8352,8353],{"class":50,"line":51},[48,8354,178],{"emptyLinePlaceholder":177},[48,8356,8357],{"class":50,"line":57},[48,8358,8359],{},"\u003Creceiver\n",[48,8361,8362],{"class":50,"line":63},[48,8363,8364],{}," android:name=\"com.google.android.gcm.GCMBroadcastReceiver\"\n",[48,8366,8367],{"class":50,"line":69},[48,8368,8369],{}," android:permission=\"com.google.android.c2dm.permission.SEND\">\n",[48,8371,8372],{"class":50,"line":75},[48,8373,8374],{}," \u003Cintent-filter>\n",[48,8376,8377],{"class":50,"line":81},[48,8378,8379],{}," \u003Caction android:name=\"com.google.android.c2dm.intent.RECEIVE\"/>\n",[48,8381,8382],{"class":50,"line":124},[48,8383,8384],{}," \u003Caction android:name=\"com.google.android.c2dm.intent.REGISTRATION\"/>\n",[48,8386,8387],{"class":50,"line":129},[48,8388,8389],{}," \u003Ccategory android:name=\"com.synyx.cloudmessagetest\"/>\n",[48,8391,8392],{"class":50,"line":204},[48,8393,8394],{}," \u003C/intent-filter>\n",[48,8396,8397],{"class":50,"line":210},[48,8398,8399],{},"\u003C/receiver>\n",[48,8401,8402],{"class":50,"line":216},[48,8403,8404],{},"\u003Cservice android:name=\".GCMIntentService\"/>\n",[18,8406,8407],{},"The GCMIntentService has to be created by us. And that is what we’ll do now. First off, the GCMIntentService has to\nextend the class GCMBaseIntentService:",[38,8409,8411],{"className":40,"code":8410,"language":42,"meta":43,"style":43},"public class GCMIntentService extends GCMBaseIntentService {\n",[45,8412,8413],{"__ignoreMap":43},[48,8414,8415],{"class":50,"line":51},[48,8416,8410],{},[18,8418,8419],{},"Now implement all the necessary methods. The only method we will use for this little test is onMessage(). We want to\nquickly see if we get a message for this app, so that we can confirm that it works. So we just create a notification\nwith the Notification Builder.",[18,8421,8422],{},"Because we are on an older minimum version of Android, we need to add the support library to have access to the\nNotification Builder. Add it by right clicking the project -> Android tools -> Add Support Library.",[18,8424,8425],{},"First in the onMessage() method, we need to get access to the Main Thread of our App.",[38,8427,8429],{"className":40,"code":8428,"language":42,"meta":43,"style":43},"Handler h = new Handler(Looper.getMainLooper());\nh.post(new Runnable() {\n public void run() {\n }\n}\n",[45,8430,8431,8436,8441,8446,8450],{"__ignoreMap":43},[48,8432,8433],{"class":50,"line":51},[48,8434,8435],{},"Handler h = new Handler(Looper.getMainLooper());\n",[48,8437,8438],{"class":50,"line":57},[48,8439,8440],{},"h.post(new Runnable() {\n",[48,8442,8443],{"class":50,"line":63},[48,8444,8445],{}," public void run() {\n",[48,8447,8448],{"class":50,"line":69},[48,8449,261],{},[48,8451,8452],{"class":50,"line":75},[48,8453,266],{},[18,8455,8456],{},"In the run() method, we get us an Intent from our MainActivity",[38,8458,8460],{"className":40,"code":8459,"language":42,"meta":43,"style":43},"Intent notificationIntent = new Intent(context, CloudMessageTestActivity.class);\n",[45,8461,8462],{"__ignoreMap":43},[48,8463,8464],{"class":50,"line":51},[48,8465,8459],{},[18,8467,8468],{},"And then wrap it with a PendingIntent for the NotificationBuilder",[38,8470,8472],{"className":40,"code":8471,"language":42,"meta":43,"style":43},"PendingIntent pendingIntent = PendingIntent.getActivity(context, 0,\n notificationIntent, 0);\n",[45,8473,8474,8479],{"__ignoreMap":43},[48,8475,8476],{"class":50,"line":51},[48,8477,8478],{},"PendingIntent pendingIntent = PendingIntent.getActivity(context, 0,\n",[48,8480,8481],{"class":50,"line":57},[48,8482,8483],{}," notificationIntent, 0);\n",[18,8485,8486],{},"Finally, use the NotificationBuilder to create and send the notification",[38,8488,8490],{"className":40,"code":8489,"language":42,"meta":43,"style":43},"NotificationCompat.Builder builder = new NotificationCompat.Builder(\n context);\nbuilder.setContentIntent(pendingIntent);\nbuilder.setAutoCancel(true);\nbuilder.setSmallIcon(R.drawable.ic_launcher);\n//this is added on the server side\nString text = intent.getStringExtra(\"text\");\nbuilder.setContentText(text);\nbuilder.setContentTitle(\"New message from the cloud!\");\nNotification noti = builder.build();\nNotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);\n//just set the mId to 1, because we don't care about it in this case\nmNotificationManager.notify(1, noti);\n",[45,8491,8492,8497,8502,8507,8512,8517,8522,8527,8532,8537,8542,8547,8552],{"__ignoreMap":43},[48,8493,8494],{"class":50,"line":51},[48,8495,8496],{},"NotificationCompat.Builder builder = new NotificationCompat.Builder(\n",[48,8498,8499],{"class":50,"line":57},[48,8500,8501],{}," context);\n",[48,8503,8504],{"class":50,"line":63},[48,8505,8506],{},"builder.setContentIntent(pendingIntent);\n",[48,8508,8509],{"class":50,"line":69},[48,8510,8511],{},"builder.setAutoCancel(true);\n",[48,8513,8514],{"class":50,"line":75},[48,8515,8516],{},"builder.setSmallIcon(R.drawable.ic_launcher);\n",[48,8518,8519],{"class":50,"line":81},[48,8520,8521],{},"//this is added on the server side\n",[48,8523,8524],{"class":50,"line":124},[48,8525,8526],{},"String text = intent.getStringExtra(\"text\");\n",[48,8528,8529],{"class":50,"line":129},[48,8530,8531],{},"builder.setContentText(text);\n",[48,8533,8534],{"class":50,"line":204},[48,8535,8536],{},"builder.setContentTitle(\"New message from the cloud!\");\n",[48,8538,8539],{"class":50,"line":210},[48,8540,8541],{},"Notification noti = builder.build();\n",[48,8543,8544],{"class":50,"line":216},[48,8545,8546],{},"NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);\n",[48,8548,8549],{"class":50,"line":357},[48,8550,8551],{},"//just set the mId to 1, because we don't care about it in this case\n",[48,8553,8554],{"class":50,"line":363},[48,8555,8556],{},"mNotificationManager.notify(1, noti);\n",[18,8558,8559],{},"That’ it with the GCMIntentService.",[18,8561,8562],{},"Now we let the app register itself with GCM in our MainActivity, which is fairly easy:",[38,8564,8566],{"className":40,"code":8565,"language":42,"meta":43,"style":43},"//set your senderId from the API here!\n private static final String SENDER_ID = \"1234567890\";\n@Override\nprotected void onCreate(Bundle savedInstanceState) {\n GCMRegistrar.checkDevice(this);\n GCMRegistrar.checkManifest(this);\n final String regId = GCMRegistrar.getRegistrationId(this);\n // if we don't have a regId yet, register at gcm\n if (regId.equals(\"\")) {\n GCMRegistrar.register(this, SENDER_ID);\n Toast.makeText(getApplicationContext(), \"Registered GCM!\", Toast.LENGTH_LONG).show();\n // just log the registrationId for this test case.\n Log.i(this.getClass().getName(), \"id: \" + GCMRegistrar.getRegistrationId(this));\n } else {\n Log.i(this.getClass().getName(), \"Already registered\");\n Toast.makeText(getApplicationContext(), \"Already registered at GCM!\", Toast.LENGTH_LONG).show();\n Log.i(this.getClass().getName(), \"id: \" + GCMRegistrar.getRegistrationId(this));\n }\n}\n",[45,8567,8568,8573,8578,8582,8587,8592,8597,8602,8607,8612,8617,8622,8627,8632,8637,8642,8647,8651,8656],{"__ignoreMap":43},[48,8569,8570],{"class":50,"line":51},[48,8571,8572],{},"//set your senderId from the API here!\n",[48,8574,8575],{"class":50,"line":57},[48,8576,8577],{}," private static final String SENDER_ID = \"1234567890\";\n",[48,8579,8580],{"class":50,"line":63},[48,8581,8161],{},[48,8583,8584],{"class":50,"line":69},[48,8585,8586],{},"protected void onCreate(Bundle savedInstanceState) {\n",[48,8588,8589],{"class":50,"line":75},[48,8590,8591],{}," GCMRegistrar.checkDevice(this);\n",[48,8593,8594],{"class":50,"line":81},[48,8595,8596],{}," GCMRegistrar.checkManifest(this);\n",[48,8598,8599],{"class":50,"line":124},[48,8600,8601],{}," final String regId = GCMRegistrar.getRegistrationId(this);\n",[48,8603,8604],{"class":50,"line":129},[48,8605,8606],{}," // if we don't have a regId yet, register at gcm\n",[48,8608,8609],{"class":50,"line":204},[48,8610,8611],{}," if (regId.equals(\"\")) {\n",[48,8613,8614],{"class":50,"line":210},[48,8615,8616],{}," GCMRegistrar.register(this, SENDER_ID);\n",[48,8618,8619],{"class":50,"line":216},[48,8620,8621],{}," Toast.makeText(getApplicationContext(), \"Registered GCM!\", Toast.LENGTH_LONG).show();\n",[48,8623,8624],{"class":50,"line":357},[48,8625,8626],{}," // just log the registrationId for this test case.\n",[48,8628,8629],{"class":50,"line":363},[48,8630,8631],{}," Log.i(this.getClass().getName(), \"id: \" + GCMRegistrar.getRegistrationId(this));\n",[48,8633,8634],{"class":50,"line":369},[48,8635,8636],{}," } else {\n",[48,8638,8639],{"class":50,"line":1978},[48,8640,8641],{}," Log.i(this.getClass().getName(), \"Already registered\");\n",[48,8643,8644],{"class":50,"line":1991},[48,8645,8646],{}," Toast.makeText(getApplicationContext(), \"Already registered at GCM!\", Toast.LENGTH_LONG).show();\n",[48,8648,8649],{"class":50,"line":2010},[48,8650,8631],{},[48,8652,8653],{"class":50,"line":2023},[48,8654,8655],{}," }\n",[48,8657,8658],{"class":50,"line":2036},[48,8659,266],{},[18,8661,8662],{},"Don’t forget to replace the SENDER_ID with yours!",[18,8664,8665],{},"I haven’t implemented a way to let the server know the registrationId, because reading it from the log seemed sufficient\nfor me in this case.",[18,8667,8668],{},"With this, we finished our small app and can begin implementing the server part.",[1822,8670,8672],{"id":8671},"the-web-application","The Web Application",[18,8674,8675],{},"For the Web Application, I created a maven web project with spring-webmvc and named it ‘GCMTestServer’. I’ll leave out\nthe config stuff for now, as everyone can use his/her favorite stack for this. The full sources with the configs are\nattached at the end of the blogpost.",[18,8677,8678,8679,6758],{},"Instead of just copying the GCM server library into the project, I searched a bit and found someone, who created a\nrepository for\nit. (",[22,8680,8681],{"href":8681,"rel":8682},"https://github.com/slorber/gcm-server-repository",[26],[18,8684,8685],{},"We start with creating the Sender class, which isn’t that hard either.",[38,8687,8689],{"className":40,"code":8688,"language":42,"meta":43,"style":43},"public class GCMSender {\n public String apiKey = null;\n public GCMSender(String apiKey) {\n this.apiKey = apiKey;\n }\n public String send(String text, String id) throws IOException {\n Sender sender = new Sender(apiKey);\n Builder builder = new Message.Builder();\n builder.addData(\"text\", text);\n Result result = sender.send(builder.build(), id, 5);\n if (result.getMessageId() != null) {\n String canonicalRegId = result.getCanonicalRegistrationId();\n if (canonicalRegId != null) {\n // same device has more than on registration ID: update database\n return \"same device has more than on registration ID: update database\";\n }\n } else {\n String error = result.getErrorCodeName();\n if (error.equals(Constants.ERROR_NOT_REGISTERED)) {\n // application has been removed from device - unregister database\n return \"application has been removed from device - unregister database\";\n }\n }\n return null;\n }\n}\n",[45,8690,8691,8696,8701,8706,8711,8716,8721,8726,8731,8736,8741,8746,8751,8756,8761,8766,8771,8776,8781,8786,8791,8796,8800,8804,8809,8813],{"__ignoreMap":43},[48,8692,8693],{"class":50,"line":51},[48,8694,8695],{},"public class GCMSender {\n",[48,8697,8698],{"class":50,"line":57},[48,8699,8700],{}," public String apiKey = null;\n",[48,8702,8703],{"class":50,"line":63},[48,8704,8705],{}," public GCMSender(String apiKey) {\n",[48,8707,8708],{"class":50,"line":69},[48,8709,8710],{}," this.apiKey = apiKey;\n",[48,8712,8713],{"class":50,"line":75},[48,8714,8715],{}," }\n",[48,8717,8718],{"class":50,"line":81},[48,8719,8720],{}," public String send(String text, String id) throws IOException {\n",[48,8722,8723],{"class":50,"line":124},[48,8724,8725],{}," Sender sender = new Sender(apiKey);\n",[48,8727,8728],{"class":50,"line":129},[48,8729,8730],{}," Builder builder = new Message.Builder();\n",[48,8732,8733],{"class":50,"line":204},[48,8734,8735],{}," builder.addData(\"text\", text);\n",[48,8737,8738],{"class":50,"line":210},[48,8739,8740],{}," Result result = sender.send(builder.build(), id, 5);\n",[48,8742,8743],{"class":50,"line":216},[48,8744,8745],{}," if (result.getMessageId() != null) {\n",[48,8747,8748],{"class":50,"line":357},[48,8749,8750],{}," String canonicalRegId = result.getCanonicalRegistrationId();\n",[48,8752,8753],{"class":50,"line":363},[48,8754,8755],{}," if (canonicalRegId != null) {\n",[48,8757,8758],{"class":50,"line":369},[48,8759,8760],{}," // same device has more than on registration ID: update database\n",[48,8762,8763],{"class":50,"line":1978},[48,8764,8765],{}," return \"same device has more than on registration ID: update database\";\n",[48,8767,8768],{"class":50,"line":1991},[48,8769,8770],{}," }\n",[48,8772,8773],{"class":50,"line":2010},[48,8774,8775],{}," } else {\n",[48,8777,8778],{"class":50,"line":2023},[48,8779,8780],{}," String error = result.getErrorCodeName();\n",[48,8782,8783],{"class":50,"line":2036},[48,8784,8785],{}," if (error.equals(Constants.ERROR_NOT_REGISTERED)) {\n",[48,8787,8788],{"class":50,"line":2048},[48,8789,8790],{}," // application has been removed from device - unregister database\n",[48,8792,8793],{"class":50,"line":2060},[48,8794,8795],{}," return \"application has been removed from device - unregister database\";\n",[48,8797,8798],{"class":50,"line":2073},[48,8799,8770],{},[48,8801,8802],{"class":50,"line":2081},[48,8803,8655],{},[48,8805,8806],{"class":50,"line":2092},[48,8807,8808],{}," return null;\n",[48,8810,8811],{"class":50,"line":2098},[48,8812,8715],{},[48,8814,8815],{"class":50,"line":2106},[48,8816,266],{},[18,8818,8819],{},"To send messages from the server, I created a small jsp page where I can enter the text and the RegistrationId of the\nuser to send the message to:",[38,8821,8823],{"className":6840,"code":8822,"language":6842,"meta":43,"style":43},"\u003C%@ taglib prefix=\"c\" uri=\"http://java.sun.com/jsp/jstl/core\" %> \u003C%@page\ncontentType=\"text/html\" pageEncoding=\"UTF-8\"%>\n\u003C!DOCTYPE html>\n\u003Chtml>\n \u003Chead>\n \u003Cmeta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n \u003Clink\n rel=\"stylesheet\"\n type=\"text/css\"\n href=\"/GCMTestServer/frontend_resources/style.css\"\n media=\"screen\"\n />\n \u003Ctitle>GCM Sender\u003C/title>\n \u003C/head>\n \u003Cbody>\n \u003Ch1>GCM Sender\u003C/h1>\n \u003Cc:if test=\"${success == true}\">\n \u003Cdiv class=\"success\">sending successful!\u003C/div>\n \u003C/c:if>\n \u003Cc:if test=\"${error == true}\">\n \u003Cdiv class=\"error\">\n Error at sending!\n \u003Cp>${errormessage}\u003C/p>\n \u003C/div>\n \u003C/c:if>\n \u003Cform method=\"POST\">\n \u003Clabel for=\"text\">Text\u003C/label\n >\u003Cinput name=\"text\" id=\"text\" type=\"text\" />\u003Cbr />\n \u003Clabel for=\"id\">Registration-Id\u003C/label\n >\u003Cinput name=\"id\" id=\"id\" type=\"text\" />\u003Cbr />\n \u003Cinput type=\"submit\" />\n \u003C/form>\n \u003C/body>\n\u003C/html>\n",[45,8824,8825,8837,8842,8856,8864,8873,8899,8906,8916,8926,8936,8946,8951,8965,8974,8983,8995,9012,9035,9044,9059,9074,9079,9093,9102,9110,9127,9148,9185,9203,9233,9248,9256,9264],{"__ignoreMap":43},[48,8826,8827,8829,8832,8834],{"class":50,"line":51},[48,8828,6849],{"class":6852},[48,8830,8831],{"class":482},"%@ taglib prefix=\"c\" uri=\"http://java.sun.com/jsp/jstl/core\" %> ",[48,8833,6849],{"class":6852},[48,8835,8836],{"class":482},"%@page\n",[48,8838,8839],{"class":50,"line":57},[48,8840,8841],{"class":482},"contentType=\"text/html\" pageEncoding=\"UTF-8\"%>\n",[48,8843,8844,8847,8851,8854],{"class":50,"line":63},[48,8845,8846],{"class":482},"\u003C!",[48,8848,8850],{"class":8849},"s9eBZ","DOCTYPE",[48,8852,8853],{"class":467}," html",[48,8855,6856],{"class":482},[48,8857,8858,8860,8862],{"class":50,"line":69},[48,8859,6849],{"class":482},[48,8861,6842],{"class":8849},[48,8863,6856],{"class":482},[48,8865,8866,8868,8871],{"class":50,"line":75},[48,8867,6861],{"class":482},[48,8869,8870],{"class":8849},"head",[48,8872,6856],{"class":482},[48,8874,8875,8878,8881,8884,8886,8889,8891,8893,8896],{"class":50,"line":81},[48,8876,8877],{"class":482}," \u003C",[48,8879,8880],{"class":8849},"meta",[48,8882,8883],{"class":467}," http-equiv",[48,8885,4007],{"class":482},[48,8887,8888],{"class":475},"\"Content-Type\"",[48,8890,685],{"class":467},[48,8892,4007],{"class":482},[48,8894,8895],{"class":475},"\"text/html; charset=UTF-8\"",[48,8897,8898],{"class":482}," />\n",[48,8900,8901,8903],{"class":50,"line":124},[48,8902,8877],{"class":482},[48,8904,8905],{"class":8849},"link\n",[48,8907,8908,8911,8913],{"class":50,"line":129},[48,8909,8910],{"class":467}," rel",[48,8912,4007],{"class":482},[48,8914,8915],{"class":475},"\"stylesheet\"\n",[48,8917,8918,8921,8923],{"class":50,"line":204},[48,8919,8920],{"class":467}," type",[48,8922,4007],{"class":482},[48,8924,8925],{"class":475},"\"text/css\"\n",[48,8927,8928,8931,8933],{"class":50,"line":210},[48,8929,8930],{"class":467}," href",[48,8932,4007],{"class":482},[48,8934,8935],{"class":475},"\"/GCMTestServer/frontend_resources/style.css\"\n",[48,8937,8938,8941,8943],{"class":50,"line":216},[48,8939,8940],{"class":467}," media",[48,8942,4007],{"class":482},[48,8944,8945],{"class":475},"\"screen\"\n",[48,8947,8948],{"class":50,"line":357},[48,8949,8950],{"class":482}," />\n",[48,8952,8953,8955,8958,8961,8963],{"class":50,"line":363},[48,8954,8877],{"class":482},[48,8956,8957],{"class":8849},"title",[48,8959,8960],{"class":482},">GCM Sender\u003C/",[48,8962,8957],{"class":8849},[48,8964,6856],{"class":482},[48,8966,8967,8970,8972],{"class":50,"line":369},[48,8968,8969],{"class":482}," \u003C/",[48,8971,8870],{"class":8849},[48,8973,6856],{"class":482},[48,8975,8976,8978,8981],{"class":50,"line":1978},[48,8977,6861],{"class":482},[48,8979,8980],{"class":8849},"body",[48,8982,6856],{"class":482},[48,8984,8985,8987,8989,8991,8993],{"class":50,"line":1991},[48,8986,8877],{"class":482},[48,8988,14],{"class":8849},[48,8990,8960],{"class":482},[48,8992,14],{"class":8849},[48,8994,6856],{"class":482},[48,8996,8997,8999,9002,9005,9007,9010],{"class":50,"line":2010},[48,8998,8877],{"class":482},[48,9000,9001],{"class":6852},"c:if",[48,9003,9004],{"class":467}," test",[48,9006,4007],{"class":482},[48,9008,9009],{"class":475},"\"${success == true}\"",[48,9011,6856],{"class":482},[48,9013,9014,9017,9020,9023,9025,9028,9031,9033],{"class":50,"line":2023},[48,9015,9016],{"class":482}," \u003C",[48,9018,9019],{"class":8849},"div",[48,9021,9022],{"class":467}," class",[48,9024,4007],{"class":482},[48,9026,9027],{"class":475},"\"success\"",[48,9029,9030],{"class":482},">sending successful!\u003C/",[48,9032,9019],{"class":8849},[48,9034,6856],{"class":482},[48,9036,9037,9040,9042],{"class":50,"line":2036},[48,9038,9039],{"class":482}," \u003C/",[48,9041,9001],{"class":6852},[48,9043,6856],{"class":482},[48,9045,9046,9048,9050,9052,9054,9057],{"class":50,"line":2048},[48,9047,8877],{"class":482},[48,9049,9001],{"class":6852},[48,9051,9004],{"class":467},[48,9053,4007],{"class":482},[48,9055,9056],{"class":475},"\"${error == true}\"",[48,9058,6856],{"class":482},[48,9060,9061,9063,9065,9067,9069,9072],{"class":50,"line":2060},[48,9062,9016],{"class":482},[48,9064,9019],{"class":8849},[48,9066,9022],{"class":467},[48,9068,4007],{"class":482},[48,9070,9071],{"class":475},"\"error\"",[48,9073,6856],{"class":482},[48,9075,9076],{"class":50,"line":2073},[48,9077,9078],{"class":482}," Error at sending!\n",[48,9080,9081,9084,9086,9089,9091],{"class":50,"line":2081},[48,9082,9083],{"class":482}," \u003C",[48,9085,18],{"class":8849},[48,9087,9088],{"class":482},">${errormessage}\u003C/",[48,9090,18],{"class":8849},[48,9092,6856],{"class":482},[48,9094,9095,9098,9100],{"class":50,"line":2092},[48,9096,9097],{"class":482}," \u003C/",[48,9099,9019],{"class":8849},[48,9101,6856],{"class":482},[48,9103,9104,9106,9108],{"class":50,"line":2098},[48,9105,9039],{"class":482},[48,9107,9001],{"class":6852},[48,9109,6856],{"class":482},[48,9111,9112,9114,9117,9120,9122,9125],{"class":50,"line":2106},[48,9113,8877],{"class":482},[48,9115,9116],{"class":8849},"form",[48,9118,9119],{"class":467}," method",[48,9121,4007],{"class":482},[48,9123,9124],{"class":475},"\"POST\"",[48,9126,6856],{"class":482},[48,9128,9129,9131,9134,9137,9139,9142,9145],{"class":50,"line":2112},[48,9130,9016],{"class":482},[48,9132,9133],{"class":8849},"label",[48,9135,9136],{"class":467}," for",[48,9138,4007],{"class":482},[48,9140,9141],{"class":475},"\"text\"",[48,9143,9144],{"class":482},">Text\u003C/",[48,9146,9147],{"class":8849},"label\n",[48,9149,9150,9153,9156,9159,9161,9163,9166,9168,9170,9173,9175,9177,9180,9183],{"class":50,"line":2117},[48,9151,9152],{"class":482}," >\u003C",[48,9154,9155],{"class":8849},"input",[48,9157,9158],{"class":467}," name",[48,9160,4007],{"class":482},[48,9162,9141],{"class":475},[48,9164,9165],{"class":467}," id",[48,9167,4007],{"class":482},[48,9169,9141],{"class":475},[48,9171,9172],{"class":467}," type",[48,9174,4007],{"class":482},[48,9176,9141],{"class":475},[48,9178,9179],{"class":482}," />\u003C",[48,9181,9182],{"class":8849},"br",[48,9184,8898],{"class":482},[48,9186,9187,9189,9191,9193,9195,9198,9201],{"class":50,"line":2129},[48,9188,9016],{"class":482},[48,9190,9133],{"class":8849},[48,9192,9136],{"class":467},[48,9194,4007],{"class":482},[48,9196,9197],{"class":475},"\"id\"",[48,9199,9200],{"class":482},">Registration-Id\u003C/",[48,9202,9147],{"class":8849},[48,9204,9205,9207,9209,9211,9213,9215,9217,9219,9221,9223,9225,9227,9229,9231],{"class":50,"line":2150},[48,9206,9152],{"class":482},[48,9208,9155],{"class":8849},[48,9210,9158],{"class":467},[48,9212,4007],{"class":482},[48,9214,9197],{"class":475},[48,9216,9165],{"class":467},[48,9218,4007],{"class":482},[48,9220,9197],{"class":475},[48,9222,9172],{"class":467},[48,9224,4007],{"class":482},[48,9226,9141],{"class":475},[48,9228,9179],{"class":482},[48,9230,9182],{"class":8849},[48,9232,8898],{"class":482},[48,9234,9235,9237,9239,9241,9243,9246],{"class":50,"line":2161},[48,9236,9016],{"class":482},[48,9238,9155],{"class":8849},[48,9240,9172],{"class":467},[48,9242,4007],{"class":482},[48,9244,9245],{"class":475},"\"submit\"",[48,9247,8898],{"class":482},[48,9249,9250,9252,9254],{"class":50,"line":2173},[48,9251,9039],{"class":482},[48,9253,9116],{"class":8849},[48,9255,6856],{"class":482},[48,9257,9258,9260,9262],{"class":50,"line":2184},[48,9259,8969],{"class":482},[48,9261,8980],{"class":8849},[48,9263,6856],{"class":482},[48,9265,9266,9268,9270],{"class":50,"line":2195},[48,9267,6895],{"class":482},[48,9269,6842],{"class":8849},[48,9271,6856],{"class":482},[18,9273,9274],{},"In the corresponding Controller to process the request, send the message:",[38,9276,9278],{"className":40,"code":9277,"language":42,"meta":43,"style":43}," @RequestMapping(value = \"send\", method = RequestMethod.POST)\n public String send(Model model,\n @RequestParam(\"text\") String text,\n @RequestParam(\"id\") String id) {\n String error = null;\n try {\n error = gcmSender.send(text, id);\n } catch (IOException ex) {\n model.addAttribute(\"error\", true);\n model.addAttribute(\"errormessage\", ex.getMessage());\n }\n if (error == null) {\n model.addAttribute(\"success\", true);\n } else {\n model.addAttribute(\"error\", true);\n model.addAttribute(\"errormessage\", error);\n }\n return \"sender\";\n }\n",[45,9279,9280,9285,9290,9295,9300,9305,9310,9315,9320,9325,9330,9334,9339,9344,9349,9353,9358,9362,9367],{"__ignoreMap":43},[48,9281,9282],{"class":50,"line":51},[48,9283,9284],{}," @RequestMapping(value = \"send\", method = RequestMethod.POST)\n",[48,9286,9287],{"class":50,"line":57},[48,9288,9289],{}," public String send(Model model,\n",[48,9291,9292],{"class":50,"line":63},[48,9293,9294],{}," @RequestParam(\"text\") String text,\n",[48,9296,9297],{"class":50,"line":69},[48,9298,9299],{}," @RequestParam(\"id\") String id) {\n",[48,9301,9302],{"class":50,"line":75},[48,9303,9304],{}," String error = null;\n",[48,9306,9307],{"class":50,"line":81},[48,9308,9309],{}," try {\n",[48,9311,9312],{"class":50,"line":124},[48,9313,9314],{}," error = gcmSender.send(text, id);\n",[48,9316,9317],{"class":50,"line":129},[48,9318,9319],{}," } catch (IOException ex) {\n",[48,9321,9322],{"class":50,"line":204},[48,9323,9324],{}," model.addAttribute(\"error\", true);\n",[48,9326,9327],{"class":50,"line":210},[48,9328,9329],{}," model.addAttribute(\"errormessage\", ex.getMessage());\n",[48,9331,9332],{"class":50,"line":216},[48,9333,2864],{},[48,9335,9336],{"class":50,"line":357},[48,9337,9338],{}," if (error == null) {\n",[48,9340,9341],{"class":50,"line":363},[48,9342,9343],{}," model.addAttribute(\"success\", true);\n",[48,9345,9346],{"class":50,"line":369},[48,9347,9348],{}," } else {\n",[48,9350,9351],{"class":50,"line":1978},[48,9352,9324],{},[48,9354,9355],{"class":50,"line":1991},[48,9356,9357],{}," model.addAttribute(\"errormessage\", error);\n",[48,9359,9360],{"class":50,"line":2010},[48,9361,2864],{},[48,9363,9364],{"class":50,"line":2023},[48,9365,9366],{}," return \"sender\";\n",[48,9368,9369],{"class":50,"line":2036},[48,9370,261],{},[18,9372,9373],{},"Now we are ready to test it!",[18,9375,9376],{},"Start the app, copy the RegistrationId from the Logs, start the Server, enter the RegistrationId and a small text, and\nthere you go:",[18,9378,9379],{},[1195,9380],{"alt":9381,"src":9382},"\"notification\"","https://media.synyx.de/uploads//2012/12/notification-300x221.jpg",[18,9384,9385],{},"To get a better understanding, you can also read the rest of the starting guide and the other documentation.",[18,9387,9388],{},"All together, it’s really easy to use the GCM libraries and the messages are beeing sent to the user very fast (around\none second for me). I haven’t tried to use it in a greater context with more users yet, but I don’t think google will\nfail on this behalf 😛",[18,9390,9391,9392],{},"As promised, here are the full sources:",[22,9393,9396],{"href":9394,"rel":9395},"https://media.synyx.de/uploads//2012/12/CloudMessageTest.zip",[26],"CloudMessageTest",[394,9398,9399],{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s7hpK, html code.shiki .s7hpK{--shiki-default:#B31D28;--shiki-default-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}",{"title":43,"searchDepth":57,"depth":57,"links":9401},[9402,9403],{"id":8266,"depth":57,"text":8267},{"id":8671,"depth":57,"text":8672},[6671,406],"2013-01-08T08:21:24","https://synyx.de/blog/a-small-look-into-google-cloud-messages/",{},"/blog/a-small-look-into-google-cloud-messages",{"title":8241,"description":8250},"blog/a-small-look-into-google-cloud-messages",[7380,9412,9413,9414,9415,9416,9417],"cloud","gcm","google-cloud","messages","messaging","push-notification","Within the scope of some Android R&D I took a look at Google’s Cloud Message Service, GCM. Well, the starter guide at http://developer.android.com/google/gcm/gs.html is almost all you need to get started,…","Zo3lVe4ZoQawpHyu6_t-mMTQUwODaiJpqsdkWTxts5M",{"id":9421,"title":9422,"author":9423,"body":9424,"category":10075,"date":10076,"description":10077,"extension":409,"link":10078,"meta":10079,"navigation":177,"path":10080,"seo":10081,"slug":9428,"stem":10082,"tags":10083,"teaser":10091,"__hash__":10092},"blog/blog/android-expandable-listview.md","Android: Expandable ListView",[6686],{"type":11,"value":9425,"toc":10073},[9426,9429,9432,9435,9623,9626,9691,9694,9755,9758,9761,9770,9773,9776,9813,9816,9831,9834,9882,9885,9956,9959,10007,10010,10035,10038,10041,10053,10063,10071],[14,9427,9422],{"id":9428},"android-expandable-listview",[18,9430,9431],{},"In today’s tutorial I’d like to show you how to implement a ListView, that only displays a limited number of entries.\nWith a button at the end of the list, the user can load more entries.",[18,9433,9434],{},"To achieve this goal, we first need to implement a basic Adapter that provides our ListView with the entries:",[38,9436,9438],{"className":40,"code":9437,"language":42,"meta":43,"style":43},"private class ExpandableListAdapter extends BaseAdapter {\n private List entries;\n private Context context;\n public ExpandableListAdapter(List entries) {\n this.entries = entries;\n }\n @Override\n public int getCount() {\n return entries.size();\n }\n @Override\n public Object getItem(int position) {\n if (position >= 0 && position \u003C entries.size())\n return this.entries.get(position);\n return null;\n }\n @Override\n public long getItemId(int position) {\n return position;\n }\n @Override\n public View getView(int position, View convertView, ViewGroup parent) {\n View view = null;\n //reuse the convertView\n if (convertView == null) {\n view = getLayoutInflater().inflate(R.layout.row, null);\n } else {\n view = convertView;\n }\n String entry = entries.get(position);\n view.setTag(position);\n fillView(view, entry);\n return view;\n }\n private void fillView(View view, String entry) {\n TextView text = (TextView) view.findViewById(R.id.text);\n text.setText(entry);\n }\n}\n",[45,9439,9440,9445,9450,9455,9460,9465,9469,9474,9479,9484,9488,9492,9497,9502,9507,9512,9516,9520,9525,9530,9534,9538,9543,9548,9553,9558,9563,9567,9572,9576,9581,9586,9591,9596,9600,9605,9610,9615,9619],{"__ignoreMap":43},[48,9441,9442],{"class":50,"line":51},[48,9443,9444],{},"private class ExpandableListAdapter extends BaseAdapter {\n",[48,9446,9447],{"class":50,"line":57},[48,9448,9449],{}," private List entries;\n",[48,9451,9452],{"class":50,"line":63},[48,9453,9454],{}," private Context context;\n",[48,9456,9457],{"class":50,"line":69},[48,9458,9459],{}," public ExpandableListAdapter(List entries) {\n",[48,9461,9462],{"class":50,"line":75},[48,9463,9464],{}," this.entries = entries;\n",[48,9466,9467],{"class":50,"line":81},[48,9468,261],{},[48,9470,9471],{"class":50,"line":124},[48,9472,9473],{}," @Override\n",[48,9475,9476],{"class":50,"line":129},[48,9477,9478],{}," public int getCount() {\n",[48,9480,9481],{"class":50,"line":204},[48,9482,9483],{}," return entries.size();\n",[48,9485,9486],{"class":50,"line":210},[48,9487,261],{},[48,9489,9490],{"class":50,"line":216},[48,9491,9473],{},[48,9493,9494],{"class":50,"line":357},[48,9495,9496],{}," public Object getItem(int position) {\n",[48,9498,9499],{"class":50,"line":363},[48,9500,9501],{}," if (position >= 0 && position \u003C entries.size())\n",[48,9503,9504],{"class":50,"line":369},[48,9505,9506],{}," return this.entries.get(position);\n",[48,9508,9509],{"class":50,"line":1978},[48,9510,9511],{}," return null;\n",[48,9513,9514],{"class":50,"line":1991},[48,9515,261],{},[48,9517,9518],{"class":50,"line":2010},[48,9519,9473],{},[48,9521,9522],{"class":50,"line":2023},[48,9523,9524],{}," public long getItemId(int position) {\n",[48,9526,9527],{"class":50,"line":2036},[48,9528,9529],{}," return position;\n",[48,9531,9532],{"class":50,"line":2048},[48,9533,261],{},[48,9535,9536],{"class":50,"line":2060},[48,9537,9473],{},[48,9539,9540],{"class":50,"line":2073},[48,9541,9542],{}," public View getView(int position, View convertView, ViewGroup parent) {\n",[48,9544,9545],{"class":50,"line":2081},[48,9546,9547],{}," View view = null;\n",[48,9549,9550],{"class":50,"line":2092},[48,9551,9552],{}," //reuse the convertView\n",[48,9554,9555],{"class":50,"line":2098},[48,9556,9557],{}," if (convertView == null) {\n",[48,9559,9560],{"class":50,"line":2106},[48,9561,9562],{}," view = getLayoutInflater().inflate(R.layout.row, null);\n",[48,9564,9565],{"class":50,"line":2112},[48,9566,9348],{},[48,9568,9569],{"class":50,"line":2117},[48,9570,9571],{}," view = convertView;\n",[48,9573,9574],{"class":50,"line":2129},[48,9575,2864],{},[48,9577,9578],{"class":50,"line":2150},[48,9579,9580],{}," String entry = entries.get(position);\n",[48,9582,9583],{"class":50,"line":2161},[48,9584,9585],{}," view.setTag(position);\n",[48,9587,9588],{"class":50,"line":2173},[48,9589,9590],{}," fillView(view, entry);\n",[48,9592,9593],{"class":50,"line":2184},[48,9594,9595],{}," return view;\n",[48,9597,9598],{"class":50,"line":2195},[48,9599,261],{},[48,9601,9602],{"class":50,"line":2207},[48,9603,9604],{}," private void fillView(View view, String entry) {\n",[48,9606,9607],{"class":50,"line":2215},[48,9608,9609],{}," TextView text = (TextView) view.findViewById(R.id.text);\n",[48,9611,9612],{"class":50,"line":2222},[48,9613,9614],{}," text.setText(entry);\n",[48,9616,9617],{"class":50,"line":2227},[48,9618,261],{},[48,9620,9621],{"class":50,"line":2232},[48,9622,266],{},[18,9624,9625],{},"For this example, I used a simple view for the rows, with just a TextView in it:",[38,9627,9629],{"className":3414,"code":9628,"language":3416,"meta":43,"style":43},"\u003C?xml version=\"1.0\" encoding=\"utf-8\"?>\n\u003CLinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:layout_width=\"match_parent\"\n android:layout_height=\"match_parent\"\n android:orientation=\"vertical\">\n \u003CTextView\n android:id=\"@+id/text\"\n android:layout_width=\"match_parent\"\n android:layout_height=\"30dp\"\n android:gravity=\"center_vertical\"\n android:padding=\"5dp\"/>\n\u003C/LinearLayout>\n",[45,9630,9631,9636,9641,9646,9651,9656,9661,9666,9671,9676,9681,9686],{"__ignoreMap":43},[48,9632,9633],{"class":50,"line":51},[48,9634,9635],{},"\u003C?xml version=\"1.0\" encoding=\"utf-8\"?>\n",[48,9637,9638],{"class":50,"line":57},[48,9639,9640],{},"\u003CLinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n",[48,9642,9643],{"class":50,"line":63},[48,9644,9645],{}," android:layout_width=\"match_parent\"\n",[48,9647,9648],{"class":50,"line":69},[48,9649,9650],{}," android:layout_height=\"match_parent\"\n",[48,9652,9653],{"class":50,"line":75},[48,9654,9655],{}," android:orientation=\"vertical\">\n",[48,9657,9658],{"class":50,"line":81},[48,9659,9660],{}," \u003CTextView\n",[48,9662,9663],{"class":50,"line":124},[48,9664,9665],{}," android:id=\"@+id/text\"\n",[48,9667,9668],{"class":50,"line":129},[48,9669,9670],{}," android:layout_width=\"match_parent\"\n",[48,9672,9673],{"class":50,"line":204},[48,9674,9675],{}," android:layout_height=\"30dp\"\n",[48,9677,9678],{"class":50,"line":210},[48,9679,9680],{}," android:gravity=\"center_vertical\"\n",[48,9682,9683],{"class":50,"line":216},[48,9684,9685],{}," android:padding=\"5dp\"/>\n",[48,9687,9688],{"class":50,"line":357},[48,9689,9690],{},"\u003C/LinearLayout>\n",[18,9692,9693],{},"Then we add the created adapter to a ListView (or a ListActivity like here):",[38,9695,9697],{"className":40,"code":9696,"language":42,"meta":43,"style":43},"public class ExpandableListActivity extends ListActivity {\n@Override\npublic void onCreate(Bundle savedInstanceState) {\n super.onCreate(savedInstanceState);\n List\u003CString> entries = new ArrayList\u003CString>();\n for (int i = 1; i \u003C= 101; i++) {\n entries.add(\"Entry \" + i);\n }\n setListAdapter(new ExpandableListAdapter(entries, this));\n}\n private class ExpandableListAdapter extends BaseAdapter{....}\n}\n",[45,9698,9699,9704,9708,9713,9718,9723,9728,9733,9737,9742,9746,9751],{"__ignoreMap":43},[48,9700,9701],{"class":50,"line":51},[48,9702,9703],{},"public class ExpandableListActivity extends ListActivity {\n",[48,9705,9706],{"class":50,"line":57},[48,9707,8161],{},[48,9709,9710],{"class":50,"line":63},[48,9711,9712],{},"public void onCreate(Bundle savedInstanceState) {\n",[48,9714,9715],{"class":50,"line":69},[48,9716,9717],{}," super.onCreate(savedInstanceState);\n",[48,9719,9720],{"class":50,"line":75},[48,9721,9722],{}," List\u003CString> entries = new ArrayList\u003CString>();\n",[48,9724,9725],{"class":50,"line":81},[48,9726,9727],{}," for (int i = 1; i \u003C= 101; i++) {\n",[48,9729,9730],{"class":50,"line":124},[48,9731,9732],{}," entries.add(\"Entry \" + i);\n",[48,9734,9735],{"class":50,"line":129},[48,9736,8655],{},[48,9738,9739],{"class":50,"line":204},[48,9740,9741],{}," setListAdapter(new ExpandableListAdapter(entries, this));\n",[48,9743,9744],{"class":50,"line":210},[48,9745,266],{},[48,9747,9748],{"class":50,"line":216},[48,9749,9750],{}," private class ExpandableListAdapter extends BaseAdapter{....}\n",[48,9752,9753],{"class":50,"line":357},[48,9754,266],{},[18,9756,9757],{},"The list as it is now shows all entries, so the next thing to do, is implementing the limiting function to the adapter.",[18,9759,9760],{},"Let’s start by giving our adapter a member variable for the limit:",[38,9762,9764],{"className":40,"code":9763,"language":42,"meta":43,"style":43},"private int limit = 20;\n",[45,9765,9766],{"__ignoreMap":43},[48,9767,9768],{"class":50,"line":51},[48,9769,9763],{},[18,9771,9772],{},"Next we need to modify some of the methods to get this working:",[18,9774,9775],{},"In getCount we need to either return the limit, or the size of the list, if the limit is greater than the list, because\nour list contains all entries and we only want to display a part of it.",[38,9777,9779],{"className":40,"code":9778,"language":42,"meta":43,"style":43},"@Override\npublic int getCount() {\n if (entries.size() \u003C= limit) {\n return entries.size();\n }\n return limit;\n}\n",[45,9780,9781,9785,9790,9795,9800,9804,9809],{"__ignoreMap":43},[48,9782,9783],{"class":50,"line":51},[48,9784,8161],{},[48,9786,9787],{"class":50,"line":57},[48,9788,9789],{},"public int getCount() {\n",[48,9791,9792],{"class":50,"line":63},[48,9793,9794],{}," if (entries.size() \u003C= limit) {\n",[48,9796,9797],{"class":50,"line":69},[48,9798,9799],{}," return entries.size();\n",[48,9801,9802],{"class":50,"line":75},[48,9803,8655],{},[48,9805,9806],{"class":50,"line":81},[48,9807,9808],{}," return limit;\n",[48,9810,9811],{"class":50,"line":124},[48,9812,266],{},[18,9814,9815],{},"Next, we need to adjust the getItem method with an further clause in the if",[38,9817,9819],{"className":40,"code":9818,"language":42,"meta":43,"style":43},"if (position >= 0 && position \u003C limit && position \u003C entries.size())\n return entries.get(position);\n",[45,9820,9821,9826],{"__ignoreMap":43},[48,9822,9823],{"class":50,"line":51},[48,9824,9825],{},"if (position >= 0 && position \u003C limit && position \u003C entries.size())\n",[48,9827,9828],{"class":50,"line":57},[48,9829,9830],{}," return entries.get(position);\n",[18,9832,9833],{},"Now for the biggest change for our new functionality: the implementation of the ‘more button’.",[38,9835,9837],{"className":40,"code":9836,"language":42,"meta":43,"style":43},"private LinearLayout getMoreView() {\n LinearLayout moreView = (LinearLayout) getLayoutInflater().inflate(R.layout.more_row, null);\n moreView.setOnClickListener(new View.OnClickListener() {\n public void onClick(View v) {\n listAdapter.increaseLimit();\n }\n });\n return moreView;\n}\n",[45,9838,9839,9844,9849,9854,9859,9864,9868,9873,9878],{"__ignoreMap":43},[48,9840,9841],{"class":50,"line":51},[48,9842,9843],{},"private LinearLayout getMoreView() {\n",[48,9845,9846],{"class":50,"line":57},[48,9847,9848],{}," LinearLayout moreView = (LinearLayout) getLayoutInflater().inflate(R.layout.more_row, null);\n",[48,9850,9851],{"class":50,"line":63},[48,9852,9853],{}," moreView.setOnClickListener(new View.OnClickListener() {\n",[48,9855,9856],{"class":50,"line":69},[48,9857,9858],{}," public void onClick(View v) {\n",[48,9860,9861],{"class":50,"line":75},[48,9862,9863],{}," listAdapter.increaseLimit();\n",[48,9865,9866],{"class":50,"line":81},[48,9867,2864],{},[48,9869,9870],{"class":50,"line":124},[48,9871,9872],{}," });\n",[48,9874,9875],{"class":50,"line":129},[48,9876,9877],{}," return moreView;\n",[48,9879,9880],{"class":50,"line":204},[48,9881,266],{},[18,9883,9884],{},"Create another simple xml like this (disregarded i18n for this simple test case. Of course, Strings should normally be\ndeclared in the strings.xml):",[38,9886,9888],{"className":3414,"code":9887,"language":3416,"meta":43,"style":43},"\u003C?xml version=\"1.0\" encoding=\"utf-8\"?>\n\u003CLinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:layout_width=\"fill_parent\"\n android:layout_height=\"wrap_content\"\n android:orientation=\"horizontal\"\n android:paddingLeft=\"20dp\"\n android:paddingRight=\"20dp\">\n \u003CTextView\n android:id=\"@+id/MoreRowText\"\n android:layout_width=\"fill_parent\"\n android:layout_height=\"40dp\"\n android:text=\"Load more...\"\n android:gravity=\"center\"/>\n\u003C/LinearLayout>\n",[45,9889,9890,9894,9898,9903,9908,9913,9918,9923,9927,9932,9937,9942,9947,9952],{"__ignoreMap":43},[48,9891,9892],{"class":50,"line":51},[48,9893,9635],{},[48,9895,9896],{"class":50,"line":57},[48,9897,9640],{},[48,9899,9900],{"class":50,"line":63},[48,9901,9902],{}," android:layout_width=\"fill_parent\"\n",[48,9904,9905],{"class":50,"line":69},[48,9906,9907],{}," android:layout_height=\"wrap_content\"\n",[48,9909,9910],{"class":50,"line":75},[48,9911,9912],{}," android:orientation=\"horizontal\"\n",[48,9914,9915],{"class":50,"line":81},[48,9916,9917],{}," android:paddingLeft=\"20dp\"\n",[48,9919,9920],{"class":50,"line":124},[48,9921,9922],{}," android:paddingRight=\"20dp\">\n",[48,9924,9925],{"class":50,"line":129},[48,9926,9660],{},[48,9928,9929],{"class":50,"line":204},[48,9930,9931],{}," android:id=\"@+id/MoreRowText\"\n",[48,9933,9934],{"class":50,"line":210},[48,9935,9936],{}," android:layout_width=\"fill_parent\"\n",[48,9938,9939],{"class":50,"line":216},[48,9940,9941],{}," android:layout_height=\"40dp\"\n",[48,9943,9944],{"class":50,"line":357},[48,9945,9946],{}," android:text=\"Load more...\"\n",[48,9948,9949],{"class":50,"line":363},[48,9950,9951],{}," android:gravity=\"center\"/>\n",[48,9953,9954],{"class":50,"line":369},[48,9955,9690],{},[18,9957,9958],{},"And add a method to the adapter to increase the limit:",[38,9960,9962],{"className":40,"code":9961,"language":42,"meta":43,"style":43}," public void increaseLimit() {\n limit+=20;\n //remove the button if we can no longer expand the list\n if (limit >= entries.size()) {\n getListView().removeFooterView(moreView);\n }\n //notify to redraw the list\n notifyDataSetChanged();\n }\n",[45,9963,9964,9969,9974,9979,9984,9989,9993,9998,10003],{"__ignoreMap":43},[48,9965,9966],{"class":50,"line":51},[48,9967,9968],{}," public void increaseLimit() {\n",[48,9970,9971],{"class":50,"line":57},[48,9972,9973],{}," limit+=20;\n",[48,9975,9976],{"class":50,"line":63},[48,9977,9978],{}," //remove the button if we can no longer expand the list\n",[48,9980,9981],{"class":50,"line":69},[48,9982,9983],{}," if (limit >= entries.size()) {\n",[48,9985,9986],{"class":50,"line":75},[48,9987,9988],{}," getListView().removeFooterView(moreView);\n",[48,9990,9991],{"class":50,"line":81},[48,9992,8655],{},[48,9994,9995],{"class":50,"line":124},[48,9996,9997],{}," //notify to redraw the list\n",[48,9999,10000],{"class":50,"line":129},[48,10001,10002],{}," notifyDataSetChanged();\n",[48,10004,10005],{"class":50,"line":204},[48,10006,8655],{},[18,10008,10009],{},"At last, we simply add the button as a footer view to our list (After creation of the ListView, but before setting the\nListAdapter to the ListView):",[38,10011,10013],{"className":40,"code":10012,"language":42,"meta":43,"style":43},"moreView = getMoreView();\nlistAdapter = new ExpandableListAdapter(entries);\ngetListView().addFooterView(moreView);\nsetListAdapter(listAdapter);\n",[45,10014,10015,10020,10025,10030],{"__ignoreMap":43},[48,10016,10017],{"class":50,"line":51},[48,10018,10019],{},"moreView = getMoreView();\n",[48,10021,10022],{"class":50,"line":57},[48,10023,10024],{},"listAdapter = new ExpandableListAdapter(entries);\n",[48,10026,10027],{"class":50,"line":63},[48,10028,10029],{},"getListView().addFooterView(moreView);\n",[48,10031,10032],{"class":50,"line":69},[48,10033,10034],{},"setListAdapter(listAdapter);\n",[18,10036,10037],{},"And that’s it for the simple case of a static list 🙂",[18,10039,10040],{},"If you want to do this a bit more dynamic or you don’t want to load whole database table (or the whole internet) into\nyour list, you have to do some extra work.",[10042,10043,10044,10047,10050],"ol",{},[871,10045,10046],{},"Load the next X entries with an AsyncTask (display a ProgressDialog while it is getting the data!) and add a method\nto your adapter, that adds this entries to the list. Call this method from within the onPostExecute method of the\nAsyncTask",[871,10048,10049],{},"You may want to keep track of the total number of entries you have to know when the user can’t load any more entries\nand to remove the button. (If the dataset is not bound to grow every few seconds. Otherwise, maybe let the user try\nto hit the button and see for himself, if theres new data)",[871,10051,10052],{},"Keep in mind to notify your users if anything fails, like the next entries could not have been loaded, because they\nhave no internet connection.",[18,10054,10055,10056,10062],{},"A Little homework for you: Combine it with\nthe ",[22,10057,10061],{"href":10058,"rel":10059,"title":10060},"http://blog.synyx.de/2011/11/android-listview-with-rounded-corners/",[26],"android listview with rounded corners","rounded corners example","\nto make it look better 😛",[18,10064,10065,10066],{},"Here’s the source, btw:",[22,10067,10070],{"href":10068,"rel":10069},"https://media.synyx.de/uploads//2012/12/ExpandableListview.zip",[26],"ExpandableListview",[394,10072,396],{},{"title":43,"searchDepth":57,"depth":57,"links":10074},[],[6671,406],"2012-12-07T09:39:26","In today’s tutorial I’d like to show you how to implement a ListView, that only displays a limited number of entries.\\nWith a button at the end of the list, the user can load more entries.","https://synyx.de/blog/android-expandable-listview/",{},"/blog/android-expandable-listview",{"title":9422,"description":9431},"blog/android-expandable-listview",[7380,10084,10085,10086,10087,10088,10089,10090],"entries","entry","expandable","limit","limited","list","listview","In today’s tutorial I’d like to show you how to implement a ListView, that only displays a limited number of entries. With a button at the end of the list,…","2cPxf8ntm24639cXpvDd1uvgjk37n-ulC-vUEA0BPZw",{"id":10094,"title":10095,"author":10096,"body":10098,"category":10738,"date":10739,"description":10740,"extension":409,"link":10741,"meta":10742,"navigation":177,"path":10743,"seo":10744,"slug":10102,"stem":10745,"tags":10746,"teaser":10747,"__hash__":10748},"blog/blog/visualize-javascript-code-quality-and-code-coverage-with-sonar.md","Visualize JavaScript code quality and code coverage with Sonar",[10097],"seber",{"type":11,"value":10099,"toc":10729},[10100,10103,10106,10112,10115,10121,10135,10138,10142,10191,10194,10208,10211,10225,10236,10239,10324,10327,10330,10333,10336,10339,10342,10344,10354,10356,10359,10362,10405,10408,10443,10446,10538,10547,10550,10553,10556,10561,10565,10572,10576,10583,10608,10612,10615,10624,10627,10636,10647,10657,10661,10664,10667,10675,10685,10689,10694,10700,10705,10710,10727],[14,10101,10095],{"id":10102},"visualize-javascript-code-quality-and-code-coverage-with-sonar",[18,10104,10105],{},"It is hard to imagine a web project without JavaScript code today. JavaScript is an easy to learn and very performant\nscript language. In the past we have used JavaScript mostly for eye-candy and form validation. Recently we have been\nasked more often to implement complex user interfaces with trees, sortable tables and things like that. So we decided to\nrely more on JavaScript to improve the feedback of the website to user actions.",[18,10107,10108,10109],{},"The first question that came up was: ",[974,10110,10111],{},"How to develop test driven with JavaScript?",[18,10113,10114],{},"We decided to use Jasmine, a behaviour driven development framework which tests can be run headless in a Maven build for\nexample.",[18,10116,10117,10118],{},"The second question was: ",[974,10119,10120],{},"How to visualise code coverage and code quality?",[18,10122,10123,10124,10130,10131,10134],{},"The tool Sonar has been proven to be useful in our Java projects in the past. So the first I was searching for was the\nJavaScript Plugin for Sonar. It can be\ndownloaded ",[22,10125,10129],{"href":10126,"rel":10127,"title":10128},"https://web.archive.org/web/20150530172810/http://docs.codehaus.org/display/SONAR/JavaScript+Plugin",[26],"Sonar JavaScript Plugin","here","\nand was luckily updated to version 1.0 recently with a bunch of new metrics like “",[275,10132,10133],{},"avoid usage of == and !=","”.",[18,10136,10137],{},"Unfortunately this plugin only supports JsTestDriver for code coverage and other test metrics. However, Jasmine support\nis on the Roadmap and I’m looking forward to see the next release of the JavaScript Plugin. But at the moment I had to\nintegrate our Jasmine and jasmine-jquery tests with JsTestDriver, the JavaScript Plugin of Sonar and an automated maven\nbuild.",[1822,10139,10141],{"id":10140},"used-technologies","Used technologies",[868,10143,10144,10151,10158,10165,10172,10179,10186],{},[871,10145,10146],{},[22,10147,10150],{"href":10148,"rel":10149,"title":10150},"https://maven.apache.org/",[26],"Maven",[871,10152,10153],{},[22,10154,10157],{"href":10155,"rel":10156,"title":10157},"https://github.com/jasmine/jasmine",[26],"Jasmine",[871,10159,10160],{},[22,10161,10164],{"href":10162,"rel":10163,"title":10164},"https://github.com/ibolmo/jasmine-jstd-adapter",[26],"jasmine-jstd-adapter",[871,10166,10167],{},[22,10168,10171],{"href":10169,"rel":10170,"title":10171},"https://code.google.com/p/js-test-driver/",[26],"JsTestDriver",[871,10173,10174],{},[22,10175,10178],{"href":10176,"rel":10177,"title":10178},"https://code.google.com/p/jstd-maven-plugin/",[26],"jstd-maven-plugin",[871,10180,10181],{},[22,10182,10185],{"href":10183,"rel":10184,"title":10185},"https://sonarsource.com/",[26],"Sonar",[871,10187,10188],{},[22,10189,10128],{"href":10126,"rel":10190,"title":10128},[26],[1822,10192,10171],{"id":10193},"jstestdriver",[18,10195,10196,10197,10201,10202,392],{},"JsTestDriver is a test runner designed by Google and can be\ndownloaded ",[22,10198,10129],{"href":10199,"rel":10200,"title":10171},"http://code.google.com/p/js-test-driver/downloads/",[26],". If you don’t know JSTestDriver\nyet, you maybe want to run over\nit’s ",[22,10203,10207],{"href":10204,"rel":10205,"title":10206},"http://code.google.com/p/js-test-driver/w/list",[26],"JsTestDriver Documentation","documentation",[18,10209,10210],{},"Some advantages of JsTestDriver:",[868,10212,10213,10216,10219,10222],{},[871,10214,10215],{},"Eclipse and IntelliJ integration",[871,10217,10218],{},"Maven Plugin",[871,10220,10221],{},"parallel test executions across browsers (local or remote)",[871,10223,10224],{},"code coverage",[18,10226,10227,10228,10231,10232,10235],{},"In order to run JsTestDriver you have to create a config file. By default you have to name it ",[275,10229,10230],{},"jsTestDriver.conf"," and\nyou have to save it in ",[275,10233,10234],{},"src/test/resources",". If you want to choose another filename or path remember to define these in\nthe Maven plugin later.",[18,10237,10238],{},"In the config file you have to define following flags (Be sure that there are no whitespaces in front of the keywords):",[38,10240,10242],{"className":7315,"code":10241,"language":7317,"meta":43,"style":43},"server: http://localhost:9876\nload:\n # jasmine dependency\n - lib/jasmine.js\n # to make our jasmine tests work within jstd\n # (must be included after jasmine.js)\n - lib/jasmineAdapter.js\n # models, views and other stuff\n - src/main/js/*.js\ntest:\n # load all test files\n - src/test/js/*Test.js\nplugin:\n - name: \"coverage\"\n jar: \"lib/coverage-1.3.4.b.jar\"\n module: \"com.google.jstestdriver.coverage.CoverageModule\"\n",[45,10243,10244,10249,10254,10259,10264,10269,10274,10279,10284,10289,10294,10299,10304,10309,10314,10319],{"__ignoreMap":43},[48,10245,10246],{"class":50,"line":51},[48,10247,10248],{},"server: http://localhost:9876\n",[48,10250,10251],{"class":50,"line":57},[48,10252,10253],{},"load:\n",[48,10255,10256],{"class":50,"line":63},[48,10257,10258],{}," # jasmine dependency\n",[48,10260,10261],{"class":50,"line":69},[48,10262,10263],{}," - lib/jasmine.js\n",[48,10265,10266],{"class":50,"line":75},[48,10267,10268],{}," # to make our jasmine tests work within jstd\n",[48,10270,10271],{"class":50,"line":81},[48,10272,10273],{}," # (must be included after jasmine.js)\n",[48,10275,10276],{"class":50,"line":124},[48,10277,10278],{}," - lib/jasmineAdapter.js\n",[48,10280,10281],{"class":50,"line":129},[48,10282,10283],{}," # models, views and other stuff\n",[48,10285,10286],{"class":50,"line":204},[48,10287,10288],{}," - src/main/js/*.js\n",[48,10290,10291],{"class":50,"line":210},[48,10292,10293],{},"test:\n",[48,10295,10296],{"class":50,"line":216},[48,10297,10298],{}," # load all test files\n",[48,10300,10301],{"class":50,"line":357},[48,10302,10303],{}," - src/test/js/*Test.js\n",[48,10305,10306],{"class":50,"line":363},[48,10307,10308],{},"plugin:\n",[48,10310,10311],{"class":50,"line":369},[48,10312,10313],{}," - name: \"coverage\"\n",[48,10315,10316],{"class":50,"line":1978},[48,10317,10318],{}," jar: \"lib/coverage-1.3.4.b.jar\"\n",[48,10320,10321],{"class":50,"line":1991},[48,10322,10323],{}," module: \"com.google.jstestdriver.coverage.CoverageModule\"\n",[635,10325,10326],{"id":10326},"server",[18,10328,10329],{},"JsTestDriver starts it’s own server and generates a HTML page that can be captured by various browsers.",[635,10331,10332],{"id":10332},"load",[18,10334,10335],{},"Define all needed JavaScript files like your models and views and so on. You can use a wildcard * to include all files\nwithin the specified directory. Note that it could be relevant to load your files in an correct order since a normal\nHTML page will be created and some dependencies have to be considered. The load sequence will be alphabetically if the\nwildcard is used.",[635,10337,10338],{"id":10338},"test",[18,10340,10341],{},"simply attaches the test files to the created HTML page.",[635,10343,3666],{"id":3666},[18,10345,10346,10347,10353],{},"to be able to see the code coverage report in Sonar you have to download\nthe ",[22,10348,10352],{"href":10349,"rel":10350,"title":10351},"http://code.google.com/p/js-test-driver/downloads/list",[26],"jstd coverage plugin","coverage plugin"," and save it\nsomewhere as you wish. Just remember to add the path to the plugin jar flag as shown in the listing above.",[1822,10355,10178],{"id":10178},[18,10357,10358],{},"Before we can see the Sonar report about the code quality and code coverage we have to configure maven to run jstd.",[18,10360,10361],{},"Unfortunately, the jstd-maven-plugin is not available at the Maven Central Repository. Therefore we have to add a new\nrepository and pluginRepository to our pom.xml:",[38,10363,10365],{"className":7315,"code":10364,"language":7317,"meta":43,"style":43},"\u003Crepository>\n \u003Cid>jstd-maven-plugin google code repo\u003C/id>\n \u003Curl>http://jstd-maven-plugin.googlecode.com/svn/maven2\u003C/url>\n\u003C/repository>\n\u003CpluginRepository>\n \u003Cid>jstd-maven-plugin google code repo\u003C/id>\n \u003Curl>http://jstd-maven-plugin.googlecode.com/svn/maven2\u003C/url>\n\u003C/pluginRepository>\n",[45,10366,10367,10372,10377,10382,10387,10392,10396,10400],{"__ignoreMap":43},[48,10368,10369],{"class":50,"line":51},[48,10370,10371],{},"\u003Crepository>\n",[48,10373,10374],{"class":50,"line":57},[48,10375,10376],{}," \u003Cid>jstd-maven-plugin google code repo\u003C/id>\n",[48,10378,10379],{"class":50,"line":63},[48,10380,10381],{}," \u003Curl>http://jstd-maven-plugin.googlecode.com/svn/maven2\u003C/url>\n",[48,10383,10384],{"class":50,"line":69},[48,10385,10386],{},"\u003C/repository>\n",[48,10388,10389],{"class":50,"line":75},[48,10390,10391],{},"\u003CpluginRepository>\n",[48,10393,10394],{"class":50,"line":81},[48,10395,10376],{},[48,10397,10398],{"class":50,"line":124},[48,10399,10381],{},[48,10401,10402],{"class":50,"line":129},[48,10403,10404],{},"\u003C/pluginRepository>\n",[18,10406,10407],{},"After that maven should be able to fetch the jstd-maven-plugin artifact:",[38,10409,10411],{"className":7315,"code":10410,"language":7317,"meta":43,"style":43},"\u003Cdependency>\n \u003CgroupId>com.googlecode.jstd-maven-plugin\u003C/groupId>\n \u003CartifactId>jstd-maven-plugin\u003C/artifactId>\n \u003Cversion>1.3.2.5\u003C/version>\n \u003Cscope>test\u003C/scope>\n\u003C/dependency>\n",[45,10412,10413,10418,10423,10428,10433,10438],{"__ignoreMap":43},[48,10414,10415],{"class":50,"line":51},[48,10416,10417],{},"\u003Cdependency>\n",[48,10419,10420],{"class":50,"line":57},[48,10421,10422],{}," \u003CgroupId>com.googlecode.jstd-maven-plugin\u003C/groupId>\n",[48,10424,10425],{"class":50,"line":63},[48,10426,10427],{}," \u003CartifactId>jstd-maven-plugin\u003C/artifactId>\n",[48,10429,10430],{"class":50,"line":69},[48,10431,10432],{}," \u003Cversion>1.3.2.5\u003C/version>\n",[48,10434,10435],{"class":50,"line":75},[48,10436,10437],{}," \u003Cscope>test\u003C/scope>\n",[48,10439,10440],{"class":50,"line":81},[48,10441,10442],{},"\u003C/dependency>\n",[18,10444,10445],{},"To run our tests with a maven build we need the jstd-maven-plugin as a Maven plugin:",[38,10447,10449],{"className":7315,"code":10448,"language":7317,"meta":43,"style":43},"\u003Cplugin>\n \u003CgroupId>com.googlecode.jstd-maven-plugin\u003C/groupId>\n \u003CartifactId>jstd-maven-plugin\u003C/artifactId>\n \u003Cversion>1.3.2.5\u003C/version>\n \u003Cconfiguration>\n \u003Cbrowser>firefox\u003C/browser>\n \u003Cport>9876\u003C/port>\n \u003CtestOutput>target/jstestdriver\u003C/testOutput>\n \u003C/configuration>\n \u003Cexecutions>\n \u003Cexecution>\n \u003Cid>run-tests\u003C/id>\n \u003Cgoals>\n \u003Cgoal>test\u003C/goal>\n \u003C/goals>\n \u003C/execution>\n \u003C/executions>\n\u003C/plugin>\n",[45,10450,10451,10456,10460,10464,10468,10473,10478,10483,10488,10493,10498,10503,10508,10513,10518,10523,10528,10533],{"__ignoreMap":43},[48,10452,10453],{"class":50,"line":51},[48,10454,10455],{},"\u003Cplugin>\n",[48,10457,10458],{"class":50,"line":57},[48,10459,10422],{},[48,10461,10462],{"class":50,"line":63},[48,10463,10427],{},[48,10465,10466],{"class":50,"line":69},[48,10467,10432],{},[48,10469,10470],{"class":50,"line":75},[48,10471,10472],{}," \u003Cconfiguration>\n",[48,10474,10475],{"class":50,"line":81},[48,10476,10477],{}," \u003Cbrowser>firefox\u003C/browser>\n",[48,10479,10480],{"class":50,"line":124},[48,10481,10482],{}," \u003Cport>9876\u003C/port>\n",[48,10484,10485],{"class":50,"line":129},[48,10486,10487],{}," \u003CtestOutput>target/jstestdriver\u003C/testOutput>\n",[48,10489,10490],{"class":50,"line":204},[48,10491,10492],{}," \u003C/configuration>\n",[48,10494,10495],{"class":50,"line":210},[48,10496,10497],{}," \u003Cexecutions>\n",[48,10499,10500],{"class":50,"line":216},[48,10501,10502],{}," \u003Cexecution>\n",[48,10504,10505],{"class":50,"line":357},[48,10506,10507],{}," \u003Cid>run-tests\u003C/id>\n",[48,10509,10510],{"class":50,"line":363},[48,10511,10512],{}," \u003Cgoals>\n",[48,10514,10515],{"class":50,"line":369},[48,10516,10517],{}," \u003Cgoal>test\u003C/goal>\n",[48,10519,10520],{"class":50,"line":1978},[48,10521,10522],{}," \u003C/goals>\n",[48,10524,10525],{"class":50,"line":1991},[48,10526,10527],{}," \u003C/execution>\n",[48,10529,10530],{"class":50,"line":2010},[48,10531,10532],{}," \u003C/executions>\n",[48,10534,10535],{"class":50,"line":2023},[48,10536,10537],{},"\u003C/plugin>\n",[18,10539,10540,10541,10546],{},"Three configuration flags are mandatory. More command line flags can be found in\nthe ",[22,10542,10207],{"href":10543,"rel":10544,"title":10545},"http://code.google.com/p/js-test-driver/wiki/CommandLineFlags",[26],"jstd-maven-plugin documentation"," of\njstd.",[635,10548,10549],{"id":10549},"browser",[18,10551,10552],{},"a comma separated list of browsers (more exactly the path to the specific browser) that should be used for the tests",[635,10554,10555],{"id":10555},"port",[18,10557,10558,10559],{},"the port that is set in ",[275,10560,10230],{},[635,10562,10564],{"id":10563},"testoutput","testOutput",[18,10566,10567,10568,10571],{},"This specifies the directory where the code coverage reports (needed for Sonar) will be saved. The default directory for\nsonar is ",[275,10569,10570],{},"target/jstestdriver",", so remember to configure sonar accordingly, if you choose another directory.",[1822,10573,10575],{"id":10574},"set-the-sourcedirectory-in-the-pomxml","Set the sourceDirectory in the pom.xml",[18,10577,10578,10579,10582],{},"In order for Sonar to be able to analyze the JavaScript code and to visualize the reports, we must add the path to the\nsource code which is ",[275,10580,10581],{},"src/main/js"," in our case.",[38,10584,10586],{"className":7315,"code":10585,"language":7317,"meta":43,"style":43},"\u003Cbuild>\n \u003CsourceDirectory>src/main/js\u003C/sourceDirectory>\n \u003C!-- ... -->\n\u003C/build>\n",[45,10587,10588,10593,10598,10603],{"__ignoreMap":43},[48,10589,10590],{"class":50,"line":51},[48,10591,10592],{},"\u003Cbuild>\n",[48,10594,10595],{"class":50,"line":57},[48,10596,10597],{}," \u003CsourceDirectory>src/main/js\u003C/sourceDirectory>\n",[48,10599,10600],{"class":50,"line":63},[48,10601,10602],{}," \u003C!-- ... -->\n",[48,10604,10605],{"class":50,"line":69},[48,10606,10607],{},"\u003C/build>\n",[1822,10609,10611],{"id":10610},"run-the-tests-and-the-analysis","Run the tests and the analysis",[18,10613,10614],{},"Everything should be configured correctly now. So just start the maven build:",[38,10616,10618],{"className":7315,"code":10617,"language":7317,"meta":43,"style":43},"mvn jstd:test\n",[45,10619,10620],{"__ignoreMap":43},[48,10621,10622],{"class":50,"line":51},[48,10623,10617],{},[18,10625,10626],{},"JsTestDriver opens the defined browsers, runs all tests and generates the code coverage report. After that we have to\nstart the sonar build:",[38,10628,10630],{"className":7315,"code":10629,"language":7317,"meta":43,"style":43},"mvn sonar:sonar -Dsonar.language=js -Dsonar.branch=js\n",[45,10631,10632],{"__ignoreMap":43},[48,10633,10634],{"class":50,"line":51},[48,10635,10629],{},[18,10637,10638,10639,10642,10643,10646],{},"To tell sonar to analyze a JavaScript project the ",[275,10640,10641],{},"sonar.language"," property is essential. If the same project should be\nanalyzed as a Java project you may want to add a branch with the property ",[275,10644,10645],{},"sonar.branch",". Otherwise the previous values\nwill be overridden with this JavaScript analysis.",[18,10648,10649,10653,10654],{},[1195,10650],{"alt":10651,"src":10652},"\"JavaScript Plugin - Sonar\"","https://media.synyx.de/uploads//2012/08/js_sonar_01.png"," ",[1195,10655],{"alt":10651,"src":10656},"https://media.synyx.de/uploads//2012/08/js_sonar_02.png",[1822,10658,10660],{"id":10659},"problems","Problems",[18,10662,10663],{},"An annoying problem is running the tests with real browsers like Firefox and Chrome. The maven build automatically\nstarts the browser and also closes it after the tests are finished. But Firefox is not correctly closed by jstd somehow…\nso the next test run fails because Firefox opens a dialog which must be closed manually. The maven build is deadlocked\nand you have to abort and rerun it…",[18,10665,10666],{},"So maybe a running Firefox process would be a workaround, I thought. Well, it kinda worked but the opened tab was not\nclosed anymore (tested on Linux and Windows). Each new test run opened a new tab and after a handful testruns the tests\nfailed because of some strange error. Closing tabs manually solved this, however. Same problem occurred with Chrome (\nVersion 22.0.1201.0 dev).",[18,10668,10669,10670,10674],{},"On one hand it is really nice to run the tests in all desired browsers, on the other hand closing tabs/browsers manually\nmakes it impossible to automate this process. So I’m really looking forward to Jasmine support of the sonar JavaScript\nPlugin to run headless tests as a maven build, just like jasmine-maven-plugin. A quick google search links to this\nproject: ",[22,10671,10672],{"href":10672,"rel":10673},"https://github.com/jwark/jstd-standalone-headless",[26]," Maybe this could be a solution… Any information is welcome,\nso if you have a working setup, please let me know.",[18,10676,10677,10678,10681,10682,10684],{},"Another problem surely is the mandatory specification of the ",[275,10679,10680],{},"sourceDirectory"," to be able to see the metrics in Sonar.\nUsually you will have a Java project with some JavaScript code. Therefore you certainly can’t pinpoint to ",[275,10683,10581],{},"\nas source directory of the project, for example. Further information is appreciated, again 🙂",[1822,10686,10688],{"id":10687},"todo","Todo",[18,10690,10691],{},[974,10692,10693],{},"automate the analysis within a Jenkins build process",[18,10695,10696,10697],{},"— ",[275,10698,10699],{},"maybe jstd tests can be run headless?",[18,10701,10696,10702],{},[275,10703,10704],{},"maybe maven profiles could be used to prevent the sourceDirectory declaration?",[18,10706,10707],{},[974,10708,10709],{},"require.js integration in jstd-unit-tests",[18,10711,10696,10712],{},[275,10713,10714,10720,10721,10726],{},[22,10715,10719],{"href":10716,"rel":10717,"title":10718},"http://requirejs.org/",[26],"require.js","RequireJS"," is a JavaScript file and module loader\nand ",[22,10722,10725],{"href":10723,"rel":10724},"https://github.com/podefr/jasmine-reqjs-jstd/wiki/how-to-setup-requirejs---jasmine---jsTestDriver",[26],"this"," should be\na good starting point.",[394,10728,396],{},{"title":43,"searchDepth":57,"depth":57,"links":10730},[10731,10732,10733,10734,10735,10736,10737],{"id":10140,"depth":57,"text":10141},{"id":10193,"depth":57,"text":10171},{"id":10178,"depth":57,"text":10178},{"id":10574,"depth":57,"text":10575},{"id":10610,"depth":57,"text":10611},{"id":10659,"depth":57,"text":10660},{"id":10687,"depth":57,"text":10688},[403,404,406],"2012-08-08T13:43:42","It is hard to imagine a web project without JavaScript code today. JavaScript is an easy to learn and very performant\\nscript language. In the past we have used JavaScript mostly for eye-candy and form validation. Recently we have been\\nasked more often to implement complex user interfaces with trees, sortable tables and things like that. So we decided to\\nrely more on JavaScript to improve the feedback of the website to user actions.","https://synyx.de/blog/visualize-javascript-code-quality-and-code-coverage-with-sonar/",{},"/blog/visualize-javascript-code-quality-and-code-coverage-with-sonar",{"title":10095,"description":10105},"blog/visualize-javascript-code-quality-and-code-coverage-with-sonar",[],"It is hard to imagine a web project without JavaScript code today. JavaScript is an easy to learn and very performant script language. In the past we have used JavaScript…","mrqNn4n0IH3ZbUdfxIC3kySm1Vw9wOmXI1qcURunod0",{"id":10750,"title":10751,"author":10752,"body":10753,"category":10967,"date":10968,"description":10969,"extension":409,"link":10970,"meta":10971,"navigation":177,"path":10972,"seo":10973,"slug":10974,"stem":10975,"tags":10976,"teaser":10981,"__hash__":10982},"blog/blog/android-2-1-sqlite-problem-with-querybuilder-and-distinct.md","Android 2.1 SQLite: problem with QueryBuilder and Distinct",[6686],{"type":11,"value":10754,"toc":10965},[10755,10758,10761,10764,10941,10948,10951,10960,10963],[14,10756,10751],{"id":10757},"android-21-sqlite-problem-with-querybuilder-and-distinct",[18,10759,10760],{},"In a recent project I encountered a problem with SQLite on android 2.1. On later versions, my code worked perfectly,\nbut on 2.1 it crashed every time when trying to get a column from a cursor.",[18,10762,10763],{},"Here’s the simplified code:",[38,10765,10767],{"className":40,"code":10766,"language":42,"meta":43,"style":43},"//member, a SQLiteOpenHelper\nBackendOpenHelper helper;\n//...\npublic List \u003CExample> getExamples(String arg){\nSQLiteQueryBuilder builder = new SQLiteQueryBuilder();\n builder.setTables(\"example e JOIN\n secondtable s ON e.id = s.example_id\");\n Map\u003CString, String> projectionMap =\n new HashMap\u003CString, String>();\n projectionMap.put(\"id\", \"e.id\");\n //... put in some more values ...\n builder.setProjectionMap(projectionMap);\n builder.setDistinct(true);\n builder.appendWhere(\" e.someRow = ? \");\n //... some more wheres ...\n SQLiteDatabase db = helper.getReadableDatabase();\n String[] selectionArgs = new String[] {\n arg\n };\n Cursor cursor = builder.query(db, null,\n null, selectionArgs, null, null, null);\n if (cursor.moveToFirst()) {\n while (cursor.isAfterLast() == false) {\n int index = cursor.getColumnIndex(\"id\");\n //on android 2.1, index is returned as -1\n //on newer versions as 1\n int id = cursor.getInt(index);\n //crashes if index is -1\n //...\n cursor.moveToNext();\n }\n }\n cursor.close();\n //...\n}\n",[45,10768,10769,10774,10779,10783,10788,10793,10798,10803,10808,10813,10818,10823,10828,10833,10838,10843,10848,10853,10858,10863,10868,10873,10878,10883,10888,10893,10898,10903,10908,10913,10918,10923,10927,10932,10937],{"__ignoreMap":43},[48,10770,10771],{"class":50,"line":51},[48,10772,10773],{},"//member, a SQLiteOpenHelper\n",[48,10775,10776],{"class":50,"line":57},[48,10777,10778],{},"BackendOpenHelper helper;\n",[48,10780,10781],{"class":50,"line":63},[48,10782,219],{},[48,10784,10785],{"class":50,"line":69},[48,10786,10787],{},"public List \u003CExample> getExamples(String arg){\n",[48,10789,10790],{"class":50,"line":75},[48,10791,10792],{},"SQLiteQueryBuilder builder = new SQLiteQueryBuilder();\n",[48,10794,10795],{"class":50,"line":81},[48,10796,10797],{}," builder.setTables(\"example e JOIN\n",[48,10799,10800],{"class":50,"line":124},[48,10801,10802],{}," secondtable s ON e.id = s.example_id\");\n",[48,10804,10805],{"class":50,"line":129},[48,10806,10807],{}," Map\u003CString, String> projectionMap =\n",[48,10809,10810],{"class":50,"line":204},[48,10811,10812],{}," new HashMap\u003CString, String>();\n",[48,10814,10815],{"class":50,"line":210},[48,10816,10817],{}," projectionMap.put(\"id\", \"e.id\");\n",[48,10819,10820],{"class":50,"line":216},[48,10821,10822],{}," //... put in some more values ...\n",[48,10824,10825],{"class":50,"line":357},[48,10826,10827],{}," builder.setProjectionMap(projectionMap);\n",[48,10829,10830],{"class":50,"line":363},[48,10831,10832],{}," builder.setDistinct(true);\n",[48,10834,10835],{"class":50,"line":369},[48,10836,10837],{}," builder.appendWhere(\" e.someRow = ? \");\n",[48,10839,10840],{"class":50,"line":1978},[48,10841,10842],{}," //... some more wheres ...\n",[48,10844,10845],{"class":50,"line":1991},[48,10846,10847],{}," SQLiteDatabase db = helper.getReadableDatabase();\n",[48,10849,10850],{"class":50,"line":2010},[48,10851,10852],{}," String[] selectionArgs = new String[] {\n",[48,10854,10855],{"class":50,"line":2023},[48,10856,10857],{}," arg\n",[48,10859,10860],{"class":50,"line":2036},[48,10861,10862],{}," };\n",[48,10864,10865],{"class":50,"line":2048},[48,10866,10867],{}," Cursor cursor = builder.query(db, null,\n",[48,10869,10870],{"class":50,"line":2060},[48,10871,10872],{}," null, selectionArgs, null, null, null);\n",[48,10874,10875],{"class":50,"line":2073},[48,10876,10877],{}," if (cursor.moveToFirst()) {\n",[48,10879,10880],{"class":50,"line":2081},[48,10881,10882],{}," while (cursor.isAfterLast() == false) {\n",[48,10884,10885],{"class":50,"line":2092},[48,10886,10887],{}," int index = cursor.getColumnIndex(\"id\");\n",[48,10889,10890],{"class":50,"line":2098},[48,10891,10892],{}," //on android 2.1, index is returned as -1\n",[48,10894,10895],{"class":50,"line":2106},[48,10896,10897],{}," //on newer versions as 1\n",[48,10899,10900],{"class":50,"line":2112},[48,10901,10902],{}," int id = cursor.getInt(index);\n",[48,10904,10905],{"class":50,"line":2117},[48,10906,10907],{}," //crashes if index is -1\n",[48,10909,10910],{"class":50,"line":2129},[48,10911,10912],{}," //...\n",[48,10914,10915],{"class":50,"line":2150},[48,10916,10917],{}," cursor.moveToNext();\n",[48,10919,10920],{"class":50,"line":2161},[48,10921,10922],{}," }\n",[48,10924,10925],{"class":50,"line":2173},[48,10926,2864],{},[48,10928,10929],{"class":50,"line":2184},[48,10930,10931],{}," cursor.close();\n",[48,10933,10934],{"class":50,"line":2195},[48,10935,10936],{}," //...\n",[48,10938,10939],{"class":50,"line":2207},[48,10940,266],{},[18,10942,10943,10944,10947],{},"After some research I found out that this apparently happens, when using ",[275,10945,10946],{},"distinct"," with the QueryBuilder on android\n2.1.",[18,10949,10950],{},"So a quick fix for this problem is to simply don’t use the getColumnIndex() method from the cursor, but instead just\naccess it by its id (Though you have to remember to change this part of the code if you make changes to your table\nrows).",[38,10952,10954],{"className":40,"code":10953,"language":42,"meta":43,"style":43}," int id = cursor.getInt(1);\n",[45,10955,10956],{"__ignoreMap":43},[48,10957,10958],{"class":50,"line":51},[48,10959,10953],{},[18,10961,10962],{},"I hope this will help someone who encounters the same problem, so he doesn’t have to search for a solution as long as I\nhad to.",[394,10964,396],{},{"title":43,"searchDepth":57,"depth":57,"links":10966},[],[6671,406],"2012-05-22T09:39:08","In a recent project I encountered a problem with SQLite on android 2.1. On later versions, my code worked perfectly,\\nbut on 2.1 it crashed every time when trying to get a column from a cursor.","https://synyx.de/blog/android-2-1-sqlite-problem-with-querybuilder-and-distinct/",{},"/blog/android-2-1-sqlite-problem-with-querybuilder-and-distinct",{"title":10751,"description":10760},"android-2-1-sqlite-problem-with-querybuilder-and-distinct","blog/android-2-1-sqlite-problem-with-querybuilder-and-distinct",[10977,7380,10978,10979,10980],"2-1","database","mobile","sqlite","In a recent project I encountered a problem with SQLite on android 2.1. On later versions, my code worked perfectly, but on 2.1 it crashed every time when trying to…","x1Vxa8-B7BB8vb0rMrFKnHNmHw0JMSkiTZ3HMChf8mU",{"id":10984,"title":10985,"author":10986,"body":10987,"category":11303,"date":11304,"description":11305,"extension":409,"link":11306,"meta":11307,"navigation":177,"path":11308,"seo":11309,"slug":10991,"stem":11310,"tags":11311,"teaser":11313,"__hash__":11314},"blog/blog/android-listview-with-rounded-corners.md","Android: ListView with rounded corners",[6686],{"type":11,"value":10988,"toc":11301},[10989,10992,10995,10998,11001,11004,11071,11074,11160,11163,11166,11209,11212,11215,11296,11299],[14,10990,10985],{"id":10991},"android-listview-with-rounded-corners",[18,10993,10994],{},"In my last project I needed to implement a ListView with rounded corners, because the app had to be supplied for Android\nand iPhone and they needed to look somewhat alike.",[18,10996,10997],{},"In this blogpost, I want to show you how I’ve implemented it and hopefully help some people who also want to use\nListViews with rounded corners:",[18,10999,11000],{},"First off, we need the drawables for the backgrounds of the Lists entries:",[18,11002,11003],{},"For the entries in the middle of the list, we don’t need rounded corners, so create a xml in your drawable folder\n“list_entry_middle.xml” with following content:",[38,11005,11007],{"className":3414,"code":11006,"language":3416,"meta":43,"style":43},"\u003C?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\u003Clayer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n \u003Citem>\n \u003Cshape>\n \u003Cstroke android:width=\"1px\" android:color=\"#ffbbbbbb\"/>\n \u003C/shape>\n \u003C/item>\n \u003Citem android:bottom=\"1dp\" android:left=\"1dp\" android:right=\"1dp\">\n \u003Cshape>\n \u003Csolid android:color=\"#ffffffff\"/>\n \u003C/shape>\n \u003C/item>\n\u003C/layer-list>\n",[45,11008,11009,11014,11019,11024,11029,11034,11039,11044,11049,11053,11058,11062,11066],{"__ignoreMap":43},[48,11010,11011],{"class":50,"line":51},[48,11012,11013],{},"\u003C?xml version=\"1.0\" encoding=\"UTF-8\"?>\n",[48,11015,11016],{"class":50,"line":57},[48,11017,11018],{},"\u003Clayer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n",[48,11020,11021],{"class":50,"line":63},[48,11022,11023],{}," \u003Citem>\n",[48,11025,11026],{"class":50,"line":69},[48,11027,11028],{}," \u003Cshape>\n",[48,11030,11031],{"class":50,"line":75},[48,11032,11033],{}," \u003Cstroke android:width=\"1px\" android:color=\"#ffbbbbbb\"/>\n",[48,11035,11036],{"class":50,"line":81},[48,11037,11038],{}," \u003C/shape>\n",[48,11040,11041],{"class":50,"line":124},[48,11042,11043],{}," \u003C/item>\n",[48,11045,11046],{"class":50,"line":129},[48,11047,11048],{}," \u003Citem android:bottom=\"1dp\" android:left=\"1dp\" android:right=\"1dp\">\n",[48,11050,11051],{"class":50,"line":204},[48,11052,11028],{},[48,11054,11055],{"class":50,"line":210},[48,11056,11057],{}," \u003Csolid android:color=\"#ffffffff\"/>\n",[48,11059,11060],{"class":50,"line":216},[48,11061,11038],{},[48,11063,11064],{"class":50,"line":357},[48,11065,11043],{},[48,11067,11068],{"class":50,"line":363},[48,11069,11070],{},"\u003C/layer-list>\n",[18,11072,11073],{},"For the rounded corners, create another xml, “rounded_corner_top.xml”:",[38,11075,11077],{"className":3414,"code":11076,"language":3416,"meta":43,"style":43},"\u003C?xml version=\"1.0\" encoding=\"utf-8\"?>\n\u003Clayer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n \u003Citem>\n \u003Cshape>\n \u003Cstroke android:width=\"1dp\" android:color=\"#ffbbbbbb\"/>\n \u003Ccorners android:topLeftRadius=\"20dp\"\n android:topRightRadius=\"20dp\"\n />\n \u003C/shape>\n \u003C/item>\n \u003Citem android:top=\"1dp\" android:left=\"1dp\" android:right=\"1dp\" android:bottom=\"1dp\">\n \u003Cshape>\n \u003Csolid android:color=\"#ffffffff\"/>\n \u003Ccorners android:topLeftRadius=\"20dp\"\n android:topRightRadius=\"20dp\"\n />\n \u003C/shape>\n \u003C/item>\n\u003C/layer-list>\n",[45,11078,11079,11083,11087,11091,11095,11100,11105,11110,11115,11119,11123,11128,11132,11136,11140,11144,11148,11152,11156],{"__ignoreMap":43},[48,11080,11081],{"class":50,"line":51},[48,11082,9635],{},[48,11084,11085],{"class":50,"line":57},[48,11086,11018],{},[48,11088,11089],{"class":50,"line":63},[48,11090,11023],{},[48,11092,11093],{"class":50,"line":69},[48,11094,11028],{},[48,11096,11097],{"class":50,"line":75},[48,11098,11099],{}," \u003Cstroke android:width=\"1dp\" android:color=\"#ffbbbbbb\"/>\n",[48,11101,11102],{"class":50,"line":81},[48,11103,11104],{}," \u003Ccorners android:topLeftRadius=\"20dp\"\n",[48,11106,11107],{"class":50,"line":124},[48,11108,11109],{}," android:topRightRadius=\"20dp\"\n",[48,11111,11112],{"class":50,"line":129},[48,11113,11114],{}," />\n",[48,11116,11117],{"class":50,"line":204},[48,11118,11038],{},[48,11120,11121],{"class":50,"line":210},[48,11122,11043],{},[48,11124,11125],{"class":50,"line":216},[48,11126,11127],{}," \u003Citem android:top=\"1dp\" android:left=\"1dp\" android:right=\"1dp\" android:bottom=\"1dp\">\n",[48,11129,11130],{"class":50,"line":357},[48,11131,11028],{},[48,11133,11134],{"class":50,"line":363},[48,11135,11057],{},[48,11137,11138],{"class":50,"line":369},[48,11139,11104],{},[48,11141,11142],{"class":50,"line":1978},[48,11143,11109],{},[48,11145,11146],{"class":50,"line":1991},[48,11147,11114],{},[48,11149,11150],{"class":50,"line":2010},[48,11151,11038],{},[48,11153,11154],{"class":50,"line":2023},[48,11155,11043],{},[48,11157,11158],{"class":50,"line":2036},[48,11159,11070],{},[18,11161,11162],{},"Implementing the bottom part is quite the same, just with bottomLeftRadius and bottomRightRadius. (maybe also create one\nwith all corners rounded, if the list only has one entry)",[18,11164,11165],{},"For better usability, also provide drawables with other colors for the different states, that the list item can have and\nreference them in another xml in the drawable folder (“selector_rounded_corner_top.xml”) as followed:",[38,11167,11169],{"className":3414,"code":11168,"language":3416,"meta":43,"style":43},"\n\u003Cselector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n \u003Citem android:drawable=\"@drawable/rounded_corner_top_click\"\n android:state_pressed=\"true\"/>\n \u003Citem android:drawable=\"@drawable/rounded_corner_top_click\"\n android:state_focused=\"true\"/>\n \u003Citem android:drawable=\"@drawable/rounded_corner_top\"/>\n\u003C/selector>\n",[45,11170,11171,11175,11180,11185,11190,11194,11199,11204],{"__ignoreMap":43},[48,11172,11173],{"class":50,"line":51},[48,11174,178],{"emptyLinePlaceholder":177},[48,11176,11177],{"class":50,"line":57},[48,11178,11179],{},"\u003Cselector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n",[48,11181,11182],{"class":50,"line":63},[48,11183,11184],{}," \u003Citem android:drawable=\"@drawable/rounded_corner_top_click\"\n",[48,11186,11187],{"class":50,"line":69},[48,11188,11189],{}," android:state_pressed=\"true\"/>\n",[48,11191,11192],{"class":50,"line":75},[48,11193,11184],{},[48,11195,11196],{"class":50,"line":81},[48,11197,11198],{}," android:state_focused=\"true\"/>\n",[48,11200,11201],{"class":50,"line":124},[48,11202,11203],{}," \u003Citem android:drawable=\"@drawable/rounded_corner_top\"/>\n",[48,11205,11206],{"class":50,"line":129},[48,11207,11208],{},"\u003C/selector>\n",[18,11210,11211],{},"Now do the same for the other backgrounds of the list.",[18,11213,11214],{},"All that is left now, is to assign the right backgrounds in our ListAdapter like following:",[38,11216,11218],{"className":40,"code":11217,"language":42,"meta":43,"style":43},"@Override\npublic View getView(int position, View convertView, ViewGroup parent) {\n //...\n //skipping the view reuse stuff\n if (position == 0 && entry_list.size() == 1) {\n view.setBackgroundResource(R.drawable.selector_rounded_corner);\n } else if (position == 0) {\n view.setBackgroundResource(R.drawable.selector_rounded_corner_top);\n } else if (position == entry_list.size() - 1) {\n view.setBackgroundResource(R.drawable.selector_rounded_corner_bottom);\n } else {\n view.setBackgroundResource(R.drawable.selector_middle);\n }\n //...\n //skipping the filling of the view\n}\n",[45,11219,11220,11224,11229,11234,11239,11244,11249,11254,11259,11264,11269,11274,11279,11283,11287,11292],{"__ignoreMap":43},[48,11221,11222],{"class":50,"line":51},[48,11223,8161],{},[48,11225,11226],{"class":50,"line":57},[48,11227,11228],{},"public View getView(int position, View convertView, ViewGroup parent) {\n",[48,11230,11231],{"class":50,"line":63},[48,11232,11233],{}," //...\n",[48,11235,11236],{"class":50,"line":69},[48,11237,11238],{}," //skipping the view reuse stuff\n",[48,11240,11241],{"class":50,"line":75},[48,11242,11243],{}," if (position == 0 && entry_list.size() == 1) {\n",[48,11245,11246],{"class":50,"line":81},[48,11247,11248],{}," view.setBackgroundResource(R.drawable.selector_rounded_corner);\n",[48,11250,11251],{"class":50,"line":124},[48,11252,11253],{}," } else if (position == 0) {\n",[48,11255,11256],{"class":50,"line":129},[48,11257,11258],{}," view.setBackgroundResource(R.drawable.selector_rounded_corner_top);\n",[48,11260,11261],{"class":50,"line":204},[48,11262,11263],{}," } else if (position == entry_list.size() - 1) {\n",[48,11265,11266],{"class":50,"line":210},[48,11267,11268],{}," view.setBackgroundResource(R.drawable.selector_rounded_corner_bottom);\n",[48,11270,11271],{"class":50,"line":216},[48,11272,11273],{}," } else {\n",[48,11275,11276],{"class":50,"line":357},[48,11277,11278],{}," view.setBackgroundResource(R.drawable.selector_middle);\n",[48,11280,11281],{"class":50,"line":363},[48,11282,261],{},[48,11284,11285],{"class":50,"line":369},[48,11286,11233],{},[48,11288,11289],{"class":50,"line":1978},[48,11290,11291],{}," //skipping the filling of the view\n",[48,11293,11294],{"class":50,"line":1991},[48,11295,266],{},[18,11297,11298],{},"Aaaand we’re done.",[394,11300,396],{},{"title":43,"searchDepth":57,"depth":57,"links":11302},[],[6671,406],"2011-11-21T10:38:22","In my last project I needed to implement a ListView with rounded corners, because the app had to be supplied for Android\\nand iPhone and they needed to look somewhat alike.","https://synyx.de/blog/android-listview-with-rounded-corners/",{},"/blog/android-listview-with-rounded-corners",{"title":10985,"description":10994},"blog/android-listview-with-rounded-corners",[7380,10089,11312,10090],"lists","In my last project I needed to implement a ListView with rounded corners, because the app had to be supplied for Android and iPhone and they needed to look somewhat…","YJnytc_0AozIrr4hpGG2OyB-5_wi2WxdvyjS_ltNk3E",{"id":11316,"title":11317,"author":11318,"body":11320,"category":11591,"date":11592,"description":11593,"extension":409,"link":11594,"meta":11595,"navigation":177,"path":11596,"seo":11597,"slug":11324,"stem":11599,"tags":11600,"teaser":11606,"__hash__":11607},"blog/blog/ui-test-automation.md","UI Test Automation",[11319],"linsin",{"type":11,"value":11321,"toc":11589},[11322,11325,11339,11348,11356,11359,11550,11557,11560,11583,11586],[14,11323,11317],{"id":11324},"ui-test-automation",[18,11326,11327,11328,11333,11334,392],{},"One of the most impressive talks for me at ",[22,11329,11332],{"href":11330,"rel":11331},"http://mobile.synyx.de/2010/06/wwdc-2010/",[26],"WWDC 2010"," was session 306 –\n“Automating Use Interface Testing with Instruments”. I’ve been wanting to check it out ever since iOS 4 was released. A\ncouple of weeks ago, I finally had a chance to give it a test ride using\nour ",[22,11335,11338],{"href":11336,"rel":11337},"http://mobile.synyx.de/2010/09/i-think-i-spider-1-0-released/",[26],"very own App “I think I spider”",[18,11340,11341,11342,11347],{},"All you need to get started is an App, Instruments and some basic JavaScript skills. Apple provides a set\nof ",[22,11343,11346],{"href":11344,"rel":11345},"http://developer.apple.com/library/ios/#documentation/ToolsLanguages/Reference/UIATargetClassReference/UIATargetClass/UIATargetClass.html",[26],"JavaScript libraries",",\nthat you can use to drive your tests and simulate user interaction. Your custom test scripts are run using the\nAutomation Instrument in Apple’s Instruments App, targeting your App either in the Simulator or on an actual device.",[18,11349,11350,11351,11355],{},"You can test almost every aspect of user interaction, using Apple’s JavaScript library. No matter if you want to test\nshaking, device orientation or the basics like tapping and swiping, you can do all that using basic JavaScript function\ncalls.\nApple’s ",[22,11352,10207],{"href":11353,"rel":11354},"https://developer.apple.com/library/mac/#documentation/DeveloperTools/Conceptual/InstrumentsUserGuide/Built-InInstruments/Built-InInstruments.html%23//apple_ref/doc/uid/TP40004652-CH6-SW75",[26],"\nis quite solid, as most of them are, and explains the process in detail.",[18,11357,11358],{},"For “I think I spider” we covered the basic use cases in terms of UI, to make sure it still works after adding new\nfeatures. Here is a basic example of the JavaScript involved to test “opening” the book, after starting the App:",[38,11360,11364],{"className":11361,"code":11362,"language":11363,"meta":43,"style":43},"language-javascript shiki shiki-themes github-light github-dark","// setup\nvar target = UIATarget.localTarget();\nvar appWindow = target.frontMostApp().mainWindow();\nvar element = target;\n// first test\nvar testName = \"Start Screen Test\";\nUIALogger.logStart(testName);\nUIALogger.logMessage(\"Tapping start screen\");\nappWindow.elements()[\"start_screen\"].tap(); // open the book\nif (appWindow.elements()[\"main_screen\"].isValid()) {\n UIALogger.logFail(testName);\n} else {\n UIALogger.logPass(testName);\n}\n","javascript",[45,11365,11366,11371,11390,11413,11425,11430,11444,11455,11469,11495,11517,11527,11537,11546],{"__ignoreMap":43},[48,11367,11368],{"class":50,"line":51},[48,11369,11370],{"class":3320},"// setup\n",[48,11372,11373,11376,11379,11381,11384,11387],{"class":50,"line":57},[48,11374,11375],{"class":500},"var",[48,11377,11378],{"class":482}," target ",[48,11380,4007],{"class":500},[48,11382,11383],{"class":482}," UIATarget.",[48,11385,11386],{"class":467},"localTarget",[48,11388,11389],{"class":482},"();\n",[48,11391,11392,11394,11397,11399,11402,11405,11408,11411],{"class":50,"line":63},[48,11393,11375],{"class":500},[48,11395,11396],{"class":482}," appWindow ",[48,11398,4007],{"class":500},[48,11400,11401],{"class":482}," target.",[48,11403,11404],{"class":467},"frontMostApp",[48,11406,11407],{"class":482},"().",[48,11409,11410],{"class":467},"mainWindow",[48,11412,11389],{"class":482},[48,11414,11415,11417,11420,11422],{"class":50,"line":69},[48,11416,11375],{"class":500},[48,11418,11419],{"class":482}," element ",[48,11421,4007],{"class":500},[48,11423,11424],{"class":482}," target;\n",[48,11426,11427],{"class":50,"line":75},[48,11428,11429],{"class":3320},"// first test\n",[48,11431,11432,11434,11437,11439,11442],{"class":50,"line":81},[48,11433,11375],{"class":500},[48,11435,11436],{"class":482}," testName ",[48,11438,4007],{"class":500},[48,11440,11441],{"class":475}," \"Start Screen Test\"",[48,11443,1149],{"class":482},[48,11445,11446,11449,11452],{"class":50,"line":124},[48,11447,11448],{"class":482},"UIALogger.",[48,11450,11451],{"class":467},"logStart",[48,11453,11454],{"class":482},"(testName);\n",[48,11456,11457,11459,11462,11464,11467],{"class":50,"line":129},[48,11458,11448],{"class":482},[48,11460,11461],{"class":467},"logMessage",[48,11463,483],{"class":482},[48,11465,11466],{"class":475},"\"Tapping start screen\"",[48,11468,495],{"class":482},[48,11470,11471,11474,11477,11480,11483,11486,11489,11492],{"class":50,"line":204},[48,11472,11473],{"class":482},"appWindow.",[48,11475,11476],{"class":467},"elements",[48,11478,11479],{"class":482},"()[",[48,11481,11482],{"class":475},"\"start_screen\"",[48,11484,11485],{"class":482},"].",[48,11487,11488],{"class":467},"tap",[48,11490,11491],{"class":482},"(); ",[48,11493,11494],{"class":3320},"// open the book\n",[48,11496,11497,11499,11502,11504,11506,11509,11511,11514],{"class":50,"line":210},[48,11498,3956],{"class":500},[48,11500,11501],{"class":482}," (appWindow.",[48,11503,11476],{"class":467},[48,11505,11479],{"class":482},[48,11507,11508],{"class":475},"\"main_screen\"",[48,11510,11485],{"class":482},[48,11512,11513],{"class":467},"isValid",[48,11515,11516],{"class":482},"()) {\n",[48,11518,11519,11522,11525],{"class":50,"line":216},[48,11520,11521],{"class":482}," UIALogger.",[48,11523,11524],{"class":467},"logFail",[48,11526,11454],{"class":482},[48,11528,11529,11532,11535],{"class":50,"line":357},[48,11530,11531],{"class":482},"} ",[48,11533,11534],{"class":500},"else",[48,11536,5901],{"class":482},[48,11538,11539,11541,11544],{"class":50,"line":363},[48,11540,11521],{"class":482},[48,11542,11543],{"class":467},"logPass",[48,11545,11454],{"class":482},[48,11547,11548],{"class":50,"line":369},[48,11549,266],{"class":482},[18,11551,11552,11553,11556],{},"You can see that the JavaScript API is quite easy to use and yet very powerful! After setting this up in the Automation\nInstrument and running it, you can see the Simulator firing up and tapping the ",[275,11554,11555],{},"UIImageView"," with the accessibility\nlabel “startscreen” after it became available. It then tests, if the main screen of the App was loaded and either passes\nor fails the test.",[18,11558,11559],{},"In order to make our App testable, we had to set the accessibility labels on the elements we wanted to reference from\nthe script. That was the only change we had to make in our code. Since you should take accessibility into consideration\nanyways, it was a reasonable effort.",[18,11561,11562,11563,11568,11569,11574,11575,11578,11579,11582],{},"Apple did an awesome job giving us developers the ability to catch regressions and make our life easier. However, there\nis room for improvement, which has been\nnicely ",[22,11564,11567],{"href":11565,"rel":11566},"http://blog.airsource.co.uk/index.php/2010/08/13/ui-automation-on-the-iphone/",[26],"summarized","\nby ",[22,11570,11573],{"href":11571,"rel":11572},"http://www.airsource.co.uk",[26],"Air Source",". For us, a missing ",[275,11576,11577],{},"UIALogger.warn"," function in the JavaScript library was\nthe biggest downside. Sometimes it’s okay for a test to fail under certain conditions, but you still want to get a\nwarning about it. We use ",[275,11580,11581],{},"UIALogger.logMessage"," for those cases as a workaround, but it’s quite easy to miss those\nlines, since they don’t stand out.",[18,11584,11585],{},"Overall, we think it’s a huge improvement to have a UI testing tool for iOS Apps at hand. There is room for improvement,\nbut the current state of UI Test Automation is already priceless!",[394,11587,11588],{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":43,"searchDepth":57,"depth":57,"links":11590},[],[6671,406],"2011-01-13T09:20:09","One of the most impressive talks for me at WWDC 2010 was session 306 –\\n“Automating Use Interface Testing with Instruments”. I’ve been wanting to check it out ever since iOS 4 was released. A\\ncouple of weeks ago, I finally had a chance to give it a test ride using\\nour very own App “I think I spider”.","https://synyx.de/blog/ui-test-automation/",{},"/blog/ui-test-automation",{"title":11317,"description":11598},"One of the most impressive talks for me at WWDC 2010 was session 306 –\n“Automating Use Interface Testing with Instruments”. I’ve been wanting to check it out ever since iOS 4 was released. A\ncouple of weeks ago, I finally had a chance to give it a test ride using\nour very own App “I think I spider”.","blog/ui-test-automation",[11601,11602,11603,11604,11605],"apple","ipad","iphone","ipod","testing","One of the most impressive talks for me at WWDC 2010 was session 306 – “Automating Use Interface Testing with Instruments”. I’ve been wanting to check it out ever since…","aYYRgsmcoAvx1nUBfqtRNIHrvF2TO_aTXNHMamZLGWQ",{"id":11609,"title":11610,"author":11611,"body":11613,"category":12080,"date":12081,"description":12082,"extension":409,"link":12083,"meta":12084,"navigation":177,"path":12085,"seo":12086,"slug":11617,"stem":12088,"tags":12089,"teaser":12092,"__hash__":12093},"blog/blog/android-roboguice-against-oncreate-boilerplate.md","Android: RoboGuice against onCreate boilerplate",[11612],"krupicka",{"type":11,"value":11614,"toc":12074},[11615,11618,11629,11633,11661,11665,11668,11853,11859,12005,12011,12015,12053,12057,12069,12072],[14,11616,11610],{"id":11617},"android-roboguice-against-oncreate-boilerplate",[18,11619,11620,11621,11624,11625,11628],{},"When prototyping Android activities with a lot of view elements, the ",[45,11622,11623],{},"onCreate"," method can quickly become cluttered.\nSetup code that simply retrieves the views from the declarative layout (by using ",[45,11626,11627],{},"findViewById(int id)",") quickly fills\nup your code. Similar, small helper classes need to be instantiated and configured, though this code is not — strictly\nspeaking — part of the actual functionality of your activity class. Time to refactor and make your activity concentrate\non it’s actual task again!",[1822,11630,11632],{"id":11631},"enter-dependency-injection","Enter dependency injection",[18,11634,11635,11636,11641,11642,11645,11646,11651,11652,11657,11658,11660],{},"A popular design pattern — usually accompanied by a framework — to get rid of object setup clutter\nis ",[22,11637,11640],{"href":11638,"rel":11639},"http://en.wikipedia.org/wiki/Dependency_injection",[26],"Dependency Injection"," (in short DI). The basic idea is, that\ninstead of pulling in all your dependencies (for example the views in our case) , a central point (the injection\ncontainer) manages and coordinates the creation of your dependencies and ",[275,11643,11644],{},"injects"," them into the objects that require\nthem. In the Java world several of these containers and frameworks exist, from the heavy weights like Spring to the very\nlightweight like ",[22,11647,11650],{"href":11648,"rel":11649},"http://code.google.com/p/google-guice/",[26],"Google Guice",". For the latter a young but promising add-on\nlibrary has come to fruition: ",[22,11653,11656],{"href":11654,"rel":11655},"http://code.google.com/p/roboguice/",[26],"RoboGuice"," makes DI available for your Android\nclasses. We will walk you through a small example, showing how it simplifies your ",[45,11659,11623],{}," method by comparing it\nbefore and after introduction of RoboGuice into your code. Afterwards we will give a short scoop on how to drop it into\nyour Android Eclipse project and start working with it right away.",[1822,11662,11664],{"id":11663},"before-and-after","Before and after",[18,11666,11667],{},"We start right away with a small code snippet from an Android activity:",[38,11669,11671],{"className":40,"code":11670,"language":42,"meta":43,"style":43},"public class SpiderActivity extends Activity {\n // [..snip..] lots of other members not needed for the example\n private RelativeLayout rating;\n private ImageView ratingStars;\n private Button infoButton;\n private TextView type;\n private TextView language;\n private TextView spider;\n private TextView translation;\n private TextView author;\n private TextView page;\n private RatingBar ratingBar;\n private Handler handler;\n protected void onCreate(Bundle savedInstanceState) {\n super.onCreate(savedInstanceState);\n setContentView(R.layout.main);\n infoButton = (Button) findViewById(R.id.InfoButton);\n infoButton.setOnClickListener(new InfosOnClickListener());\n rating = (RelativeLayout) findViewById(R.id.ratingDialog);\n ratingStars = (ImageView) findViewById(R.id.ratingStars);\n ratingStars.setOnClickListener(new MenuOnClickListener());\n type = (TextView) findViewById(R.id.TypeText);\n language = (TextView) findViewById(R.id.LanguageText);\n spider = (TextView) findViewById(R.id.SpiderText);\n translation = (TextView) findViewById(R.id.TranslationText);\n author = (TextView) findViewById(R.id.AuthorText);\n page = (TextView) findViewById(R.id.PageText);\n ratingBar = (RatingBar) findViewById(R.id.ratingBar);\n if (hasUnusualDeviceDimensions()) {\n adjustLayout();\n }\n if (!isLayoutSupported()) {\n Toast.makeText(this.getApplicationContext(),\n \"Sorry, this phone resolution is not supported.\",\n Toast.LENGTH_LONG).show();\n }\n }\n",[45,11672,11673,11678,11683,11688,11693,11698,11703,11708,11713,11718,11723,11728,11733,11738,11742,11746,11751,11756,11761,11766,11771,11776,11781,11786,11791,11796,11801,11806,11811,11816,11821,11825,11830,11835,11840,11845,11849],{"__ignoreMap":43},[48,11674,11675],{"class":50,"line":51},[48,11676,11677],{},"public class SpiderActivity extends Activity {\n",[48,11679,11680],{"class":50,"line":57},[48,11681,11682],{}," // [..snip..] lots of other members not needed for the example\n",[48,11684,11685],{"class":50,"line":63},[48,11686,11687],{}," private RelativeLayout rating;\n",[48,11689,11690],{"class":50,"line":69},[48,11691,11692],{}," private ImageView ratingStars;\n",[48,11694,11695],{"class":50,"line":75},[48,11696,11697],{}," private Button infoButton;\n",[48,11699,11700],{"class":50,"line":81},[48,11701,11702],{}," private TextView type;\n",[48,11704,11705],{"class":50,"line":124},[48,11706,11707],{}," private TextView language;\n",[48,11709,11710],{"class":50,"line":129},[48,11711,11712],{}," private TextView spider;\n",[48,11714,11715],{"class":50,"line":204},[48,11716,11717],{}," private TextView translation;\n",[48,11719,11720],{"class":50,"line":210},[48,11721,11722],{}," private TextView author;\n",[48,11724,11725],{"class":50,"line":216},[48,11726,11727],{}," private TextView page;\n",[48,11729,11730],{"class":50,"line":357},[48,11731,11732],{}," private RatingBar ratingBar;\n",[48,11734,11735],{"class":50,"line":363},[48,11736,11737],{}," private Handler handler;\n",[48,11739,11740],{"class":50,"line":369},[48,11741,8166],{},[48,11743,11744],{"class":50,"line":1978},[48,11745,8200],{},[48,11747,11748],{"class":50,"line":1991},[48,11749,11750],{}," setContentView(R.layout.main);\n",[48,11752,11753],{"class":50,"line":2010},[48,11754,11755],{}," infoButton = (Button) findViewById(R.id.InfoButton);\n",[48,11757,11758],{"class":50,"line":2023},[48,11759,11760],{}," infoButton.setOnClickListener(new InfosOnClickListener());\n",[48,11762,11763],{"class":50,"line":2036},[48,11764,11765],{}," rating = (RelativeLayout) findViewById(R.id.ratingDialog);\n",[48,11767,11768],{"class":50,"line":2048},[48,11769,11770],{}," ratingStars = (ImageView) findViewById(R.id.ratingStars);\n",[48,11772,11773],{"class":50,"line":2060},[48,11774,11775],{}," ratingStars.setOnClickListener(new MenuOnClickListener());\n",[48,11777,11778],{"class":50,"line":2073},[48,11779,11780],{}," type = (TextView) findViewById(R.id.TypeText);\n",[48,11782,11783],{"class":50,"line":2081},[48,11784,11785],{}," language = (TextView) findViewById(R.id.LanguageText);\n",[48,11787,11788],{"class":50,"line":2092},[48,11789,11790],{}," spider = (TextView) findViewById(R.id.SpiderText);\n",[48,11792,11793],{"class":50,"line":2098},[48,11794,11795],{}," translation = (TextView) findViewById(R.id.TranslationText);\n",[48,11797,11798],{"class":50,"line":2106},[48,11799,11800],{}," author = (TextView) findViewById(R.id.AuthorText);\n",[48,11802,11803],{"class":50,"line":2112},[48,11804,11805],{}," page = (TextView) findViewById(R.id.PageText);\n",[48,11807,11808],{"class":50,"line":2117},[48,11809,11810],{}," ratingBar = (RatingBar) findViewById(R.id.ratingBar);\n",[48,11812,11813],{"class":50,"line":2129},[48,11814,11815],{}," if (hasUnusualDeviceDimensions()) {\n",[48,11817,11818],{"class":50,"line":2150},[48,11819,11820],{}," adjustLayout();\n",[48,11822,11823],{"class":50,"line":2161},[48,11824,2864],{},[48,11826,11827],{"class":50,"line":2173},[48,11828,11829],{}," if (!isLayoutSupported()) {\n",[48,11831,11832],{"class":50,"line":2184},[48,11833,11834],{}," Toast.makeText(this.getApplicationContext(),\n",[48,11836,11837],{"class":50,"line":2195},[48,11838,11839],{}," \"Sorry, this phone resolution is not supported.\",\n",[48,11841,11842],{"class":50,"line":2207},[48,11843,11844],{}," Toast.LENGTH_LONG).show();\n",[48,11846,11847],{"class":50,"line":2215},[48,11848,2864],{},[48,11850,11851],{"class":50,"line":2222},[48,11852,261],{},[18,11854,11855,11856,11858],{},"It’s easy to see, that the code that is actually doing something useful — check for unusual device dimensions and adjust\nlayout parameters — is only a small part of the ",[45,11857,11623],{}," method. Let’s see how this would look like with RoboGuice\napplied to your project:",[38,11860,11862],{"className":40,"code":11861,"language":42,"meta":43,"style":43},"// these are the needed classes from the RoboGuice framework\nimport roboguice.activity.RoboActivity;\nimport roboguice.inject.InjectView;\n// [..snip..]\npublic class SpiderActivity extends RoboActivity {\n @InjectView(R.id.ratingDialog ) private RelativeLayout rating;\n @InjectView(R.id.ratingStars) private ImageView ratingStars;\n @InjectView(R.id.InfoButton) private Button infoButton;\n @InjectView(R.id.TypeText) private TextView type;\n @InjectView(R.id.LanguageText) private TextView language;\n @InjectView(R.id.SpiderText) private TextView spider;\n @InjectView(R.id.TranslationText) private TextView translation;\n @InjectView(R.id.AuthorText) private TextView author;\n @InjectView(R.id.PageText) private TextView page;\n @InjectView(R.id.ratingBar) private RatingBar ratingBar;\n protected void onCreate(Bundle savedInstanceState) {\n super.onCreate(savedInstanceState);\n // the following call actually injects your views ...\n setContentView(R.layout.main);\n // they are available afterwards\n infoButton.setOnClickListener(new InfosOnClickListener());\n ratingStars.setOnClickListener(new MenuOnClickListener());\n if (hasUnusualDeviceDimensions()) {\n adjustLayout();\n }\n if (!isLayoutSupported()) {\n Toast.makeText(this.getApplicationContext(),\n \"Sorry, this phone resolution is not supported.\",\n Toast.LENGTH_LONG).show();\n }\n }\n",[45,11863,11864,11869,11874,11879,11884,11889,11894,11899,11904,11909,11914,11919,11924,11929,11934,11939,11943,11947,11952,11956,11961,11965,11969,11973,11977,11981,11985,11989,11993,11997,12001],{"__ignoreMap":43},[48,11865,11866],{"class":50,"line":51},[48,11867,11868],{},"// these are the needed classes from the RoboGuice framework\n",[48,11870,11871],{"class":50,"line":57},[48,11872,11873],{},"import roboguice.activity.RoboActivity;\n",[48,11875,11876],{"class":50,"line":63},[48,11877,11878],{},"import roboguice.inject.InjectView;\n",[48,11880,11881],{"class":50,"line":69},[48,11882,11883],{},"// [..snip..]\n",[48,11885,11886],{"class":50,"line":75},[48,11887,11888],{},"public class SpiderActivity extends RoboActivity {\n",[48,11890,11891],{"class":50,"line":81},[48,11892,11893],{}," @InjectView(R.id.ratingDialog ) private RelativeLayout rating;\n",[48,11895,11896],{"class":50,"line":124},[48,11897,11898],{}," @InjectView(R.id.ratingStars) private ImageView ratingStars;\n",[48,11900,11901],{"class":50,"line":129},[48,11902,11903],{}," @InjectView(R.id.InfoButton) private Button infoButton;\n",[48,11905,11906],{"class":50,"line":204},[48,11907,11908],{}," @InjectView(R.id.TypeText) private TextView type;\n",[48,11910,11911],{"class":50,"line":210},[48,11912,11913],{}," @InjectView(R.id.LanguageText) private TextView language;\n",[48,11915,11916],{"class":50,"line":216},[48,11917,11918],{}," @InjectView(R.id.SpiderText) private TextView spider;\n",[48,11920,11921],{"class":50,"line":357},[48,11922,11923],{}," @InjectView(R.id.TranslationText) private TextView translation;\n",[48,11925,11926],{"class":50,"line":363},[48,11927,11928],{}," @InjectView(R.id.AuthorText) private TextView author;\n",[48,11930,11931],{"class":50,"line":369},[48,11932,11933],{}," @InjectView(R.id.PageText) private TextView page;\n",[48,11935,11936],{"class":50,"line":1978},[48,11937,11938],{}," @InjectView(R.id.ratingBar) private RatingBar ratingBar;\n",[48,11940,11941],{"class":50,"line":1991},[48,11942,8166],{},[48,11944,11945],{"class":50,"line":2010},[48,11946,8200],{},[48,11948,11949],{"class":50,"line":2023},[48,11950,11951],{}," // the following call actually injects your views ...\n",[48,11953,11954],{"class":50,"line":2036},[48,11955,11750],{},[48,11957,11958],{"class":50,"line":2048},[48,11959,11960],{}," // they are available afterwards\n",[48,11962,11963],{"class":50,"line":2060},[48,11964,11760],{},[48,11966,11967],{"class":50,"line":2073},[48,11968,11775],{},[48,11970,11971],{"class":50,"line":2081},[48,11972,11815],{},[48,11974,11975],{"class":50,"line":2092},[48,11976,11820],{},[48,11978,11979],{"class":50,"line":2098},[48,11980,2864],{},[48,11982,11983],{"class":50,"line":2106},[48,11984,11829],{},[48,11986,11987],{"class":50,"line":2112},[48,11988,11834],{},[48,11990,11991],{"class":50,"line":2117},[48,11992,11839],{},[48,11994,11995],{"class":50,"line":2129},[48,11996,11844],{},[48,11998,11999],{"class":50,"line":2150},[48,12000,2864],{},[48,12002,12003],{"class":50,"line":2161},[48,12004,261],{},[18,12006,12007,12008,12010],{},"See how the ",[45,12009,11623],{}," method now communicates more clearly what it actually does? At the same time we made the\nassociation between the member variables for the views and their counterpart in the declarative layout more visible, by\nputting this information right where the member is declared. This makes the code instantly more accessible.",[1822,12012,12014],{"id":12013},"setting-up-roboguice","Setting up RoboGuice",[18,12016,12017,12018,12023,12024,12028,12029,12032,12033,12036,12037,12040,12041,12044,12045,12048,12049,12052],{},"Now to get you up and running with RoboGuice, we will give quick instructions on how to obtain the necessary\ndependencies and how to configure your Android Eclipse project. First we need the JAR files:\ndownload ",[22,12019,12022],{"href":12020,"rel":12021},"https://web.archive.org/web/20160713065637/http://google-guice.googlecode.com:80/files/guice-2.0-no_aop.jar",[26],"Guice 2.0"," (\nwhich is the base for RoboGuice)\nand ",[22,12025,11656],{"href":12026,"rel":12027},"http://download.java.net/maven/2/roboguice/roboguice/1.1-SNAPSHOT/roboguice-1.1-20100408.222944-3.jar",[26]," (\nin our example we used the development version 1.1) into a ",[45,12030,12031],{},"lib"," subdirectory of your project. Add both the libraries\nto your build path by selecting ",[974,12034,12035],{},"Build Path > Add to Build Path"," from the context menu of each of the libraries.\nFinally a small change to your ",[45,12038,12039],{},"AndroidManifest.xml"," will be needed, to make RoboGuice work. In the ",[974,12042,12043],{},"Application"," tab\nset the ",[974,12046,12047],{},"Name"," attribute to ",[45,12050,12051],{},"roboguice.application.RoboApplication"," (or make your own Application class inherit from\nit).",[1822,12054,12056],{"id":12055},"finishing-words","Finishing words",[18,12058,12059,12060,12064,12065,12068],{},"While this is only an appetizer for using Dependency Injection in your Android application and making your code more\nexpressive, there are still more resources to be found on the corresponding project pages:\nthe ",[22,12061,12063],{"href":11648,"rel":12062},[26],"Guice"," pages introduce the general idioms of the framework, while\nthe ",[22,12066,11656],{"href":11654,"rel":12067},[26]," pages contain some more examples and the documentation.",[18,12070,12071],{},"I hope this short introduction proves useful and helps you making your applications code more readable and testable in\nthe end.",[394,12073,396],{},{"title":43,"searchDepth":57,"depth":57,"links":12075},[12076,12077,12078,12079],{"id":11631,"depth":57,"text":11632},{"id":11663,"depth":57,"text":11664},{"id":12013,"depth":57,"text":12014},{"id":12055,"depth":57,"text":12056},[6671,406],"2010-09-17T18:06:36","When prototyping Android activities with a lot of view elements, the onCreate method can quickly become cluttered.\\nSetup code that simply retrieves the views from the declarative layout (by using findViewById(int id)) quickly fills\\nup your code. Similar, small helper classes need to be instantiated and configured, though this code is not — strictly\\nspeaking — part of the actual functionality of your activity class. Time to refactor and make your activity concentrate\\non it’s actual task again!","https://synyx.de/blog/android-roboguice-against-oncreate-boilerplate/",{},"/blog/android-roboguice-against-oncreate-boilerplate",{"title":11610,"description":12087},"When prototyping Android activities with a lot of view elements, the onCreate method can quickly become cluttered.\nSetup code that simply retrieves the views from the declarative layout (by using findViewById(int id)) quickly fills\nup your code. Similar, small helper classes need to be instantiated and configured, though this code is not — strictly\nspeaking — part of the actual functionality of your activity class. Time to refactor and make your activity concentrate\non it’s actual task again!","blog/android-roboguice-against-oncreate-boilerplate",[7380,12090,12091,406],"guice","roboguice","When prototyping Android activities with a lot of view elements, the onCreate method can quickly become cluttered. Setup code that simply retrieves the views from the declarative layout (by using…","WWGtuzKM-rsAWuMzV5nJuXOh5e4_kiJ7iFMPe0sPkMY",{"id":12095,"title":12096,"author":12097,"body":12098,"category":12111,"date":12113,"description":43,"extension":409,"link":12114,"meta":12115,"navigation":177,"path":12116,"seo":12117,"slug":12102,"stem":12118,"tags":12119,"teaser":12122,"__hash__":12123},"blog/blog/apn-device-tokens.md","APN Device Tokens",[11319],{"type":11,"value":12099,"toc":12109},[12100,12103],[14,12101,12096],{"id":12102},"apn-device-tokens",[18,12104,12105],{},[1195,12106],{"alt":12107,"src":12108},"I Think I Spider","https://media.synyx.de/uploads//2010/07/512px.png",{"title":43,"searchDepth":57,"depth":57,"links":12110},[],[6671,12112,406],"our-apps","2010-09-14T06:46:26","https://synyx.de/blog/apn-device-tokens/",{},"/blog/apn-device-tokens",{"title":12096,"description":43},"blog/apn-device-tokens",[12120,11601,11602,11603,12121],"apn","push-notifications","When you enable Apple Push Notifications (APN) for your App, your device generates a unique device token and pass it to the didRegisterForRemoteNotificationsWithDeviceToken method in your App delegate. Usually, you’ll…","aMPN6FYFi3_lDwUhQjGwaJBpcUeq7eDCk1IVolOP3Vg",{"id":12125,"title":12126,"author":12127,"body":12128,"category":12222,"date":12223,"description":43,"extension":409,"link":12224,"meta":12225,"navigation":177,"path":12226,"seo":12227,"slug":12228,"stem":12229,"tags":12230,"teaser":12234,"__hash__":12235},"blog/blog/resolutions-on-android.md","Android resolution and layout problems",[6686],{"type":11,"value":12129,"toc":12216},[12130,12133,12137,12141,12144,12147,12150,12153,12156,12160,12163,12166,12172,12175,12179,12182,12185,12188,12191,12194,12197,12200,12203,12206,12210,12213],[14,12131,12126],{"id":12132},"android-resolution-and-layout-problems",[18,12134,12135],{},[1195,12136],{"alt":12107,"src":12108},[1822,12138,12140],{"id":12139},"dp-or-not-dp","dp or not dp?",[18,12142,12143],{},"It was some work, but after a little time, the layout fitted for each density – well, at least so it seemed…",[18,12145,12146],{},"It fitted only for the three default dpi values (120, 160 and 240).",[18,12148,12149],{},"If you used a larger / smaller screen with the same density, the positions didn’t match exactly any more.",[18,12151,12152],{},"The problem was, that we used (as we are also said to always use) dp to set the views. It’s true that its nice to do\nthis, if you use the standard widgets and if you don’t have such a highly customized layout, but in our case it seemed\nlike the wrong decision to use dp.",[18,12154,12155],{},"The solution to this was to use pixel values instead and to create the layouts for the different resolutions and not the\ndensities. The new values were easy to calculate, because you only needed to take the values of the 480x320px\nresolution (exactly the same values as in dp) and multiply them by 0.75 for the 320x240px resolution and by 1.5 for\nthe 854×480 one (and adjust the height a little here…).",[1822,12157,12159],{"id":12158},"changing-the-layout-in-your-code","Changing the layout in your code",[18,12161,12162],{},"The next problem was (even before we converted the values in px) to display the layout on the 800×480 and 854×480\nresolutions, because you can’t declare layouts for both of them – they always take the same one.",[18,12164,12165],{},"We also didn’t want to have a black border for the bigger resolution, scaling the background to a bigger length was also\nno problem, so we decided to adjust the layout for this particular case in our code:",[38,12167,12170],{"className":12168,"code":12169,"language":6388},[6387],"\n//called in onCreate()\nif (getWindowManager().getDefaultDisplay().getHeight() == 800) {\n AbsoluteLayout.LayoutParams params;\n params = (LayoutParams) findViewById(R.id.someView).getLayoutParams();\n params.y = params.y - 7;\n// more adjusting here...\n}\n\n",[45,12171,12169],{"__ignoreMap":43},[18,12173,12174],{},"Well, maybe it isn’t a good solution, but we didn’t find any other possibility here.",[1822,12176,12178],{"id":12177},"using-resource-qualifiers-for-the-different-resolutions","Using resource qualifiers for the different resolutions",[18,12180,12181],{},"A downside of this approach is that you have to add adjustments to every resolution that is available now and that comes\nout in the future, like the bigger ones for tablets.",[18,12183,12184],{},"Right now, you can support most of the resolutions by simply declaring different layouts by using different folders (\nexcept for 800×480 and 854×480), but I’m not sure how it is going to be with new resolutions.",[18,12186,12187],{},"The layouts we declare are seperated in the following folders:",[18,12189,12190],{},"layout-normal-mdpi -> 320×480",[18,12192,12193],{},"layout-normal-hdpi -> 800×480 and 854×480 (adjusted in the code)",[18,12195,12196],{},"layout-normal-ldpi -> 400×240",[18,12198,12199],{},"layout-large-mdpi -> 800×480 tablet (mostly the same as the layout-normal-hdpi layout, but needs to be in this\nfolder)",[18,12201,12202],{},"layout-small-ldpi -> 320×240",[18,12204,12205],{},"Also one -landscape folder for each of them for the landscape layout for the widget (the rest of the app is portrait\nonly)",[1822,12207,12209],{"id":12208},"conclusion","Conclusion",[18,12211,12212],{},"If someone downloads the App with an unsupported resolution, it will not be displayed correctly. For such cases it could\nbe helpful if you could declare the resolutions that your app supports, or at least the aspect ratios and to have\nresource directory qualifiers for this, so that you don’t have to adjust your layout in the code.",[18,12214,12215],{},"My conclusion of this is that the wide variety of different resolutions, densities and especially aspect ratios makes it\nreally hard to create a good looking app that supports all of them. Google should really have put more restrictions to\nthese terms to make it easier for the developers so that they can provide better quality apps for the users.",{"title":43,"searchDepth":57,"depth":57,"links":12217},[12218,12219,12220,12221],{"id":12139,"depth":57,"text":12140},{"id":12158,"depth":57,"text":12159},{"id":12177,"depth":57,"text":12178},{"id":12208,"depth":57,"text":12209},[6671,12112,406],"2010-09-08T13:28:24","https://synyx.de/blog/resolutions-on-android/",{},"/blog/resolutions-on-android",{"title":12126,"description":43},"resolutions-on-android","blog/resolutions-on-android",[7380,12231,12232,8231,12233],"i-think-i-spider","ithinkispider","resolution","During the process of developing I think I spider we discovered various problems regarding Android’s different resolutions. Here you can see which problems we encountered and how we solved them:…","zE1O2G5yrUPOJAy03zZlQOFstFCOfkCJc5_scwvunN8",{"id":12237,"title":12238,"author":12239,"body":12240,"category":12333,"date":12334,"description":12335,"extension":409,"link":12336,"meta":12337,"navigation":177,"path":12338,"seo":12339,"slug":12244,"stem":12341,"tags":12342,"teaser":12343,"__hash__":12344},"blog/blog/testing-apps-with-in-app-purchases-in-simulator.md","Testing Apps with In App Purchases in Simulator",[11319],{"type":11,"value":12241,"toc":12331},[12242,12245,12259,12265,12274,12326,12329],[14,12243,12238],{"id":12244},"testing-apps-with-in-app-purchases-in-simulator",[18,12246,12247,12248,12253,12254,3723],{},"If you add a store to your app and\nuse ",[22,12249,12252],{"href":12250,"rel":12251},"http://developer.apple.com/iphone/library/documentation/NetworkingInternet/Conceptual/StoreKitGuide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40008267-CH1-SW1",[26],"In App Purchases","\nto collect your payments, there are a couple of limitations your have to live with. One of those limitations\nis ",[22,12255,12258],{"href":12256,"rel":12257},"http://developer.apple.com/iphone/library/documentation/NetworkingInternet/Conceptual/StoreKitGuide/DevelopingwithStoreKit/DevelopingwithStoreKit.html#//apple_ref/doc/uid/TP40008267-CH103-SW1",[26],"not being able to fully test your App in the iPhone Simulator",[12260,12261,12262],"blockquote",{},[18,12263,12264],{},"Store Kit does not operate in iPhone Simulator. When running your application in iPhone Simulator, Store Kit logs a\nwarning if your application attempts to retrieve the payment queue. Testing the store must be done on actual devices.",[18,12266,12267,12268,12273],{},"Although, there is not way to test Store Kit itself, you can still test the parts of your App that use and build on the\ninformation retrieved from Store Kit. You can use\na ",[22,12269,12272],{"href":12270,"rel":12271},"http://en.wikipedia.org/wiki/C_preprocessor#Macro_definition_and_expansion",[26],"preprocessor conditional inclusion"," to\ndetermine, whether you are running on the simulator and then “mock” the Store Kit calls or don’t execute them at all.",[38,12275,12279],{"className":12276,"code":12277,"language":12278,"meta":43,"style":43},"language-objc shiki shiki-themes github-light github-dark","#if TARGET_IPHONE_SIMULATOR\n// mock product description\n#else\nSKProductsRequest *productRequest =\n[[SKProductsRequest alloc] initWithProductIdentifiers:productIds];\nproductRequest.delegate = self;\n[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;\n[productRequest start];\n#endif\n","objc",[45,12280,12281,12286,12291,12296,12301,12306,12311,12316,12321],{"__ignoreMap":43},[48,12282,12283],{"class":50,"line":51},[48,12284,12285],{},"#if TARGET_IPHONE_SIMULATOR\n",[48,12287,12288],{"class":50,"line":57},[48,12289,12290],{},"// mock product description\n",[48,12292,12293],{"class":50,"line":63},[48,12294,12295],{},"#else\n",[48,12297,12298],{"class":50,"line":69},[48,12299,12300],{},"SKProductsRequest *productRequest =\n",[48,12302,12303],{"class":50,"line":75},[48,12304,12305],{},"[[SKProductsRequest alloc] initWithProductIdentifiers:productIds];\n",[48,12307,12308],{"class":50,"line":81},[48,12309,12310],{},"productRequest.delegate = self;\n",[48,12312,12313],{"class":50,"line":124},[48,12314,12315],{},"[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;\n",[48,12317,12318],{"class":50,"line":129},[48,12319,12320],{},"[productRequest start];\n",[48,12322,12323],{"class":50,"line":204},[48,12324,12325],{},"#endif\n",[18,12327,12328],{},"Keep in mind: this might not work for your App, however, it did work for our Apps and it’s better than not testing your\ncode at all. The optimal solution would definitely be to connect to the In App Purchase Sandbox environment from the\nSimulator.",[394,12330,396],{},{"title":43,"searchDepth":57,"depth":57,"links":12332},[],[6671,406],"2010-08-30T06:58:00","If you add a store to your app and\\nuse In App Purchases\\nto collect your payments, there are a couple of limitations your have to live with. One of those limitations\\nis not being able to fully test your App in the iPhone Simulator:","https://synyx.de/blog/testing-apps-with-in-app-purchases-in-simulator/",{},"/blog/testing-apps-with-in-app-purchases-in-simulator",{"title":12238,"description":12340},"If you add a store to your app and\nuse In App Purchases\nto collect your payments, there are a couple of limitations your have to live with. One of those limitations\nis not being able to fully test your App in the iPhone Simulator:","blog/testing-apps-with-in-app-purchases-in-simulator",[11601,11603,11605],"If you add a store to your app and use In App Purchases to collect your payments, there are a couple of limitations your have to live with. One of…","7mAd46W8XK_I2XpLyq6_9JiT3PH3jNVW-hswC_i86eU",{"id":12346,"title":12347,"author":12348,"body":12349,"category":12471,"date":12472,"description":43,"extension":409,"link":12473,"meta":12474,"navigation":177,"path":12475,"seo":12476,"slug":12353,"stem":12477,"tags":12478,"teaser":12479,"__hash__":12480},"blog/blog/settings-bundle-and-default-values.md","Settings Bundle and Default Values",[11319],{"type":11,"value":12350,"toc":12469},[12351,12354,12358,12407,12420,12453,12464,12467],[14,12352,12347],{"id":12353},"settings-bundle-and-default-values",[18,12355,12356],{},[1195,12357],{"alt":12107,"src":12108},[38,12359,12361],{"className":3414,"code":12360,"language":3416,"meta":43,"style":43},"\n\u003Ckey>Type\u003C/key>\n\u003Cstring>PSToggleSwitchSpecifier\u003C/string>\n\u003Ckey>Title\u003C/key>\n\u003Cstring>Sound\u003C/string>\n\u003Ckey>Key\u003C/key>\n\u003Cstring>sound_enabled\u003C/string>\n\u003Ckey>DefaultValue\u003C/key>\n\u003Ctrue/>\n\n",[45,12362,12363,12367,12372,12377,12382,12387,12392,12397,12402],{"__ignoreMap":43},[48,12364,12365],{"class":50,"line":51},[48,12366,178],{"emptyLinePlaceholder":177},[48,12368,12369],{"class":50,"line":57},[48,12370,12371],{},"\u003Ckey>Type\u003C/key>\n",[48,12373,12374],{"class":50,"line":63},[48,12375,12376],{},"\u003Cstring>PSToggleSwitchSpecifier\u003C/string>\n",[48,12378,12379],{"class":50,"line":69},[48,12380,12381],{},"\u003Ckey>Title\u003C/key>\n",[48,12383,12384],{"class":50,"line":75},[48,12385,12386],{},"\u003Cstring>Sound\u003C/string>\n",[48,12388,12389],{"class":50,"line":81},[48,12390,12391],{},"\u003Ckey>Key\u003C/key>\n",[48,12393,12394],{"class":50,"line":124},[48,12395,12396],{},"\u003Cstring>sound_enabled\u003C/string>\n",[48,12398,12399],{"class":50,"line":129},[48,12400,12401],{},"\u003Ckey>DefaultValue\u003C/key>\n",[48,12403,12404],{"class":50,"line":204},[48,12405,12406],{},"\u003Ctrue/>\n",[18,12408,12409,12410,12415,12416,12419],{},"Unfortuntately, the default value is only applied the first time you access the Settings Application. That means your\ncode cannot rely on the default values and rather has to check manually, if the value has been set in the Settings\nApplication. In case of “I think I spider”, that meant no sound until you accessed the Settings Application. After\npoking around for a couple of minutes,\nwe ",[22,12411,12414],{"href":12412,"rel":12413},"http://stackoverflow.com/questions/510216/can-you-make-the-settings-in-settings-bundle-default-even-if-you-dont-open-the-s/510329#510329",[26],"found a workaround",",\nwhich we implemented in our AppDelegate’s ",[275,12417,12418],{},"didFinishLaunchingWithOptions"," method:",[38,12421,12423],{"className":12276,"code":12422,"language":12278,"meta":43,"style":43},"\nid test = [[NSUserDefaults standardUserDefaults] objectForKey:@\"sound_enabled\"];\nif (test == NULL) {\n [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@\"sound_enabled\"];\n}\nreturn YES;\n",[45,12424,12425,12429,12434,12439,12444,12448],{"__ignoreMap":43},[48,12426,12427],{"class":50,"line":51},[48,12428,178],{"emptyLinePlaceholder":177},[48,12430,12431],{"class":50,"line":57},[48,12432,12433],{},"id test = [[NSUserDefaults standardUserDefaults] objectForKey:@\"sound_enabled\"];\n",[48,12435,12436],{"class":50,"line":63},[48,12437,12438],{},"if (test == NULL) {\n",[48,12440,12441],{"class":50,"line":69},[48,12442,12443],{}," [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@\"sound_enabled\"];\n",[48,12445,12446],{"class":50,"line":75},[48,12447,266],{},[48,12449,12450],{"class":50,"line":81},[48,12451,12452],{},"return YES;\n",[18,12454,12455,12456,12459,12460,12463],{},"This checks if no value was set, which means the returned value is ",[275,12457,12458],{},"NULL",", and in that case sets our default value. Note\nthat we are duplicating the default value definition here. You could also access the ",[275,12461,12462],{},"Settings Bundle"," programmatically\nand read the default value from there.",[18,12465,12466],{},"This gives us an easy workaround, so that you can enjoy the awesome sound effects in “I think I spider”.",[394,12468,396],{},{"title":43,"searchDepth":57,"depth":57,"links":12470},[],[6671,12112,406],"2010-08-16T06:38:08","https://synyx.de/blog/settings-bundle-and-default-values/",{},"/blog/settings-bundle-and-default-values",{"title":12347,"description":43},"blog/settings-bundle-and-default-values",[11601,11603],"We improved our “I think I spider” App quite a bit since Beta 1. Among other stuff we added some nice sound effects. If you want to check it out,…","2u74Bt9iJYUS2yCQZEziD6_UMO-mCCEs4rBEu1KVQJQ",{"id":12482,"title":12483,"author":12484,"body":12485,"category":14016,"date":14017,"description":14018,"extension":409,"link":14019,"meta":14020,"navigation":177,"path":14021,"seo":14022,"slug":12489,"stem":14024,"tags":14025,"teaser":14026,"__hash__":14027},"blog/blog/on-cross-device-mobile-development-part-1.md","On cross-device mobile development – Part 1",[11612],{"type":11,"value":12486,"toc":14010},[12487,12490,12497,12501,12504,12508,12516,12821,12824,13517,13520,13725,13736,13787,13790,13958,13962,13971,13974,13983,13987,13990,14004,14007],[14,12488,12483],{"id":12489},"on-cross-device-mobile-development-part-1",[18,12491,12492,12493,12496],{},"Once in a while a demand for fast development of a mobile application for several platforms at once comes up. Your team\nof developers might be small or knowledge about the different Software Development Kits (SDKs) involved is scarce. But\nhey, perhaps you know how to write HTML & CSS and are also experienced with JavaScript. With the advent of the Apple\niPhone and briefly afterwards the Android line of phones, mobile devices started to support a lot of modern HTML & CSS\nfeatures. The progressing “applification” of the WWW further boosted the development of fast JavaScript engines. And\nwith the recent addition of multitouch, geolocation and fast CSS3 animation support, the mobile browser has become a new\ndeployment target for mobile applications. ",[275,12494,12495],{},"That’s the theory at least."," In this series of articles we will provide an\noverview on the technologies involved, available frameworks and the approaches taken to bring your application to\nseveral mobile platforms at once.",[1822,12498,12500],{"id":12499},"its-not-in-the-browser","It’s (not) in the browser",[18,12502,12503],{},"Developing applications with HTML, CSS & Javascript is a fundamentally different experience. It already is on the\nnormal desktop with its widescreen browsers. On a mobile device it becomes a game changer. We are accustomed to\ngraphical user interface (GUI) toolkits with their more direct manipulation of data through on-screen elements like\nbuttons. While browsers and HTML provide their own set of UI elements and the ability to wire events like clicks to\nJavascript callbacks, they lack a lot of the bells and whistles. Especially in the handling of client-side data — the\nmodel in model-view-controller (MVC) so to say — and the controller abstraction even modern browsers lack the feature\nrichness of common native SDKs. In the rest of this article we will give a short overview on which web technologies map\nto which concept in the MVC approach and how you would basically structure an application based on such technologies.",[1822,12505,12507],{"id":12506},"of-models-views-controllers","Of models, views & controllers",[18,12509,6694,12510,12515],{},[22,12511,12514],{"href":12512,"rel":12513},"http://building-iphone-apps.labs.oreilly.com/ch04.html",[26],"“Building iPhone Apps with HTML, CSS and JavaScript”","\nJonathan Stark describes how to turn websites into single page iPhone applications. Views are implemented straight\nforward with plain HTML and CSS, throwing in some bits and pieces to make browser standard behavior of HTML elements\nmore native like. To give an example, we will show how to create a graphical navigation bar and switch between different\nscreens. First we need some HTML and CSS snippets to create the layout:",[38,12517,12519],{"className":6840,"code":12518,"language":6842,"meta":43,"style":43},"\u003Cul id=\"navigation\">\n \u003Cli>\u003Ca class=\"current\" href=\"#home\">Home\u003C/a>\u003C/li>\n \u003Cli>\u003Ca href=\"#new\">New things\u003C/a>\u003C/li>\n \u003Cli>\u003Ca href=\"#favorites\">I like those\u003C/a>\u003C/li>\n \u003Cli>\u003Ca href=\"#more\">More content\u003C/a>\u003C/li>\n \u003Cli>\u003Ca href=\"#info\">About\u003C/a>\u003C/li>\n\u003C/ul>\n\u003Cdiv class=\"view\" id=\"home\">...\u003C/div>\n\u003Cdiv class=\"view\" id=\"new\">...\u003C/div>\n\u003Cdiv class=\"view\" id=\"favorites\">...\u003C/div>\n\u003Cdiv class=\"view\" id=\"more\">...\u003C/div>\n\u003Cdiv class=\"view\" id=\"info\">...\u003C/div>\n",[45,12520,12521,12536,12574,12602,12630,12658,12686,12694,12721,12746,12771,12796],{"__ignoreMap":43},[48,12522,12523,12525,12527,12529,12531,12534],{"class":50,"line":51},[48,12524,6849],{"class":482},[48,12526,868],{"class":8849},[48,12528,9165],{"class":467},[48,12530,4007],{"class":482},[48,12532,12533],{"class":475},"\"navigation\"",[48,12535,6856],{"class":482},[48,12537,12538,12540,12542,12545,12547,12549,12551,12554,12557,12559,12562,12565,12567,12570,12572],{"class":50,"line":57},[48,12539,6861],{"class":482},[48,12541,871],{"class":8849},[48,12543,12544],{"class":482},">\u003C",[48,12546,22],{"class":8849},[48,12548,9022],{"class":467},[48,12550,4007],{"class":482},[48,12552,12553],{"class":475},"\"current\"",[48,12555,12556],{"class":467}," href",[48,12558,4007],{"class":482},[48,12560,12561],{"class":475},"\"#home\"",[48,12563,12564],{"class":482},">Home\u003C/",[48,12566,22],{"class":8849},[48,12568,12569],{"class":482},">\u003C/",[48,12571,871],{"class":8849},[48,12573,6856],{"class":482},[48,12575,12576,12578,12580,12582,12584,12586,12588,12591,12594,12596,12598,12600],{"class":50,"line":63},[48,12577,6861],{"class":482},[48,12579,871],{"class":8849},[48,12581,12544],{"class":482},[48,12583,22],{"class":8849},[48,12585,12556],{"class":467},[48,12587,4007],{"class":482},[48,12589,12590],{"class":475},"\"#new\"",[48,12592,12593],{"class":482},">New things\u003C/",[48,12595,22],{"class":8849},[48,12597,12569],{"class":482},[48,12599,871],{"class":8849},[48,12601,6856],{"class":482},[48,12603,12604,12606,12608,12610,12612,12614,12616,12619,12622,12624,12626,12628],{"class":50,"line":69},[48,12605,6861],{"class":482},[48,12607,871],{"class":8849},[48,12609,12544],{"class":482},[48,12611,22],{"class":8849},[48,12613,12556],{"class":467},[48,12615,4007],{"class":482},[48,12617,12618],{"class":475},"\"#favorites\"",[48,12620,12621],{"class":482},">I like those\u003C/",[48,12623,22],{"class":8849},[48,12625,12569],{"class":482},[48,12627,871],{"class":8849},[48,12629,6856],{"class":482},[48,12631,12632,12634,12636,12638,12640,12642,12644,12647,12650,12652,12654,12656],{"class":50,"line":75},[48,12633,6861],{"class":482},[48,12635,871],{"class":8849},[48,12637,12544],{"class":482},[48,12639,22],{"class":8849},[48,12641,12556],{"class":467},[48,12643,4007],{"class":482},[48,12645,12646],{"class":475},"\"#more\"",[48,12648,12649],{"class":482},">More content\u003C/",[48,12651,22],{"class":8849},[48,12653,12569],{"class":482},[48,12655,871],{"class":8849},[48,12657,6856],{"class":482},[48,12659,12660,12662,12664,12666,12668,12670,12672,12675,12678,12680,12682,12684],{"class":50,"line":81},[48,12661,6861],{"class":482},[48,12663,871],{"class":8849},[48,12665,12544],{"class":482},[48,12667,22],{"class":8849},[48,12669,12556],{"class":467},[48,12671,4007],{"class":482},[48,12673,12674],{"class":475},"\"#info\"",[48,12676,12677],{"class":482},">About\u003C/",[48,12679,22],{"class":8849},[48,12681,12569],{"class":482},[48,12683,871],{"class":8849},[48,12685,6856],{"class":482},[48,12687,12688,12690,12692],{"class":50,"line":124},[48,12689,6895],{"class":482},[48,12691,868],{"class":8849},[48,12693,6856],{"class":482},[48,12695,12696,12698,12700,12702,12704,12707,12709,12711,12714,12717,12719],{"class":50,"line":129},[48,12697,6849],{"class":482},[48,12699,9019],{"class":8849},[48,12701,9022],{"class":467},[48,12703,4007],{"class":482},[48,12705,12706],{"class":475},"\"view\"",[48,12708,9165],{"class":467},[48,12710,4007],{"class":482},[48,12712,12713],{"class":475},"\"home\"",[48,12715,12716],{"class":482},">...\u003C/",[48,12718,9019],{"class":8849},[48,12720,6856],{"class":482},[48,12722,12723,12725,12727,12729,12731,12733,12735,12737,12740,12742,12744],{"class":50,"line":204},[48,12724,6849],{"class":482},[48,12726,9019],{"class":8849},[48,12728,9022],{"class":467},[48,12730,4007],{"class":482},[48,12732,12706],{"class":475},[48,12734,9165],{"class":467},[48,12736,4007],{"class":482},[48,12738,12739],{"class":475},"\"new\"",[48,12741,12716],{"class":482},[48,12743,9019],{"class":8849},[48,12745,6856],{"class":482},[48,12747,12748,12750,12752,12754,12756,12758,12760,12762,12765,12767,12769],{"class":50,"line":210},[48,12749,6849],{"class":482},[48,12751,9019],{"class":8849},[48,12753,9022],{"class":467},[48,12755,4007],{"class":482},[48,12757,12706],{"class":475},[48,12759,9165],{"class":467},[48,12761,4007],{"class":482},[48,12763,12764],{"class":475},"\"favorites\"",[48,12766,12716],{"class":482},[48,12768,9019],{"class":8849},[48,12770,6856],{"class":482},[48,12772,12773,12775,12777,12779,12781,12783,12785,12787,12790,12792,12794],{"class":50,"line":216},[48,12774,6849],{"class":482},[48,12776,9019],{"class":8849},[48,12778,9022],{"class":467},[48,12780,4007],{"class":482},[48,12782,12706],{"class":475},[48,12784,9165],{"class":467},[48,12786,4007],{"class":482},[48,12788,12789],{"class":475},"\"more\"",[48,12791,12716],{"class":482},[48,12793,9019],{"class":8849},[48,12795,6856],{"class":482},[48,12797,12798,12800,12802,12804,12806,12808,12810,12812,12815,12817,12819],{"class":50,"line":357},[48,12799,6849],{"class":482},[48,12801,9019],{"class":8849},[48,12803,9022],{"class":467},[48,12805,4007],{"class":482},[48,12807,12706],{"class":475},[48,12809,9165],{"class":467},[48,12811,4007],{"class":482},[48,12813,12814],{"class":475},"\"info\"",[48,12816,12716],{"class":482},[48,12818,9019],{"class":8849},[48,12820,6856],{"class":482},[18,12822,12823],{},"The navigational items are then replaced with nice button graphics via CSS and positioned on the page.",[38,12825,12829],{"className":12826,"code":12827,"language":12828,"meta":43,"style":43},"language-css shiki shiki-themes github-light github-dark","/* navigation is a fixed block */\n#navigation {\n position: fixed;\n top: 0px;\n left: 0px;\n width: 320px;\n margin: 0;\n padding: 0;\n}\n#navigation li {\n display: block;\n position: absolute;\n}\n#navigation li a {\n display: block;\n position: absolute;\n height: 48px;\n width: 80px;\n top: 0px;\n text-indent: -9999px;\n}\na[href=\"#home\"] {\n background: url(home.png);\n left: 0px;\n}\na[href=\"#home\"].current {\n background: url(home-current.png);\n}\na[href=\"#new\"] {\n background: url(new.png);\n left: 80px;\n}\na[href=\"#new\"].current {\n background: url(new-current.png);\n}\na[href=\"#favorites\"] {\n background: url(favorites.png);\n left: 160px;\n}\na[href=\"#favorites\"].current {\n background: url(favorites-current.png);\n}\na[href=\"#more\"] {\n background: url(more.png);\n left: 240px;\n}\na[href=\"#info\"] {\n background: url(info.png);\n position: fixed;\n width: 48px;\n height: 48px;\n bottom: 16px;\n right: 16px;\n}\n/* finally position the views themselves */\ndiv.view {\n position: absolute;\n top: 48px;\n left: 0px;\n width: 320px;\n height: 396px;\n}\n","css",[45,12830,12831,12836,12843,12855,12869,12882,12896,12907,12918,12922,12931,12943,12954,12958,12969,12979,12989,13003,13016,13028,13042,13046,13063,13080,13092,13096,13116,13131,13135,13149,13164,13176,13180,13198,13213,13217,13231,13246,13259,13263,13281,13296,13300,13314,13329,13342,13346,13360,13375,13385,13397,13409,13423,13436,13440,13445,13454,13464,13476,13488,13500,13513],{"__ignoreMap":43},[48,12832,12833],{"class":50,"line":51},[48,12834,12835],{"class":3320},"/* navigation is a fixed block */\n",[48,12837,12838,12841],{"class":50,"line":57},[48,12839,12840],{"class":467},"#navigation",[48,12842,5901],{"class":482},[48,12844,12845,12848,12850,12853],{"class":50,"line":63},[48,12846,12847],{"class":471}," position",[48,12849,1863],{"class":482},[48,12851,12852],{"class":471},"fixed",[48,12854,1149],{"class":482},[48,12856,12857,12860,12862,12864,12867],{"class":50,"line":69},[48,12858,12859],{"class":471}," top",[48,12861,1863],{"class":482},[48,12863,1899],{"class":471},[48,12865,12866],{"class":500},"px",[48,12868,1149],{"class":482},[48,12870,12871,12874,12876,12878,12880],{"class":50,"line":75},[48,12872,12873],{"class":471}," left",[48,12875,1863],{"class":482},[48,12877,1899],{"class":471},[48,12879,12866],{"class":500},[48,12881,1149],{"class":482},[48,12883,12884,12887,12889,12892,12894],{"class":50,"line":81},[48,12885,12886],{"class":471}," width",[48,12888,1863],{"class":482},[48,12890,12891],{"class":471},"320",[48,12893,12866],{"class":500},[48,12895,1149],{"class":482},[48,12897,12898,12901,12903,12905],{"class":50,"line":124},[48,12899,12900],{"class":471}," margin",[48,12902,1863],{"class":482},[48,12904,1899],{"class":471},[48,12906,1149],{"class":482},[48,12908,12909,12912,12914,12916],{"class":50,"line":129},[48,12910,12911],{"class":471}," padding",[48,12913,1863],{"class":482},[48,12915,1899],{"class":471},[48,12917,1149],{"class":482},[48,12919,12920],{"class":50,"line":204},[48,12921,266],{"class":482},[48,12923,12924,12926,12929],{"class":50,"line":210},[48,12925,12840],{"class":467},[48,12927,12928],{"class":8849}," li",[48,12930,5901],{"class":482},[48,12932,12933,12936,12938,12941],{"class":50,"line":216},[48,12934,12935],{"class":471}," display",[48,12937,1863],{"class":482},[48,12939,12940],{"class":471},"block",[48,12942,1149],{"class":482},[48,12944,12945,12947,12949,12952],{"class":50,"line":357},[48,12946,12847],{"class":471},[48,12948,1863],{"class":482},[48,12950,12951],{"class":471},"absolute",[48,12953,1149],{"class":482},[48,12955,12956],{"class":50,"line":363},[48,12957,266],{"class":482},[48,12959,12960,12962,12964,12967],{"class":50,"line":369},[48,12961,12840],{"class":467},[48,12963,12928],{"class":8849},[48,12965,12966],{"class":8849}," a",[48,12968,5901],{"class":482},[48,12970,12971,12973,12975,12977],{"class":50,"line":1978},[48,12972,12935],{"class":471},[48,12974,1863],{"class":482},[48,12976,12940],{"class":471},[48,12978,1149],{"class":482},[48,12980,12981,12983,12985,12987],{"class":50,"line":1991},[48,12982,12847],{"class":471},[48,12984,1863],{"class":482},[48,12986,12951],{"class":471},[48,12988,1149],{"class":482},[48,12990,12991,12994,12996,12999,13001],{"class":50,"line":2010},[48,12992,12993],{"class":471}," height",[48,12995,1863],{"class":482},[48,12997,12998],{"class":471},"48",[48,13000,12866],{"class":500},[48,13002,1149],{"class":482},[48,13004,13005,13007,13009,13012,13014],{"class":50,"line":2023},[48,13006,12886],{"class":471},[48,13008,1863],{"class":482},[48,13010,13011],{"class":471},"80",[48,13013,12866],{"class":500},[48,13015,1149],{"class":482},[48,13017,13018,13020,13022,13024,13026],{"class":50,"line":2036},[48,13019,12859],{"class":471},[48,13021,1863],{"class":482},[48,13023,1899],{"class":471},[48,13025,12866],{"class":500},[48,13027,1149],{"class":482},[48,13029,13030,13033,13035,13038,13040],{"class":50,"line":2048},[48,13031,13032],{"class":471}," text-indent",[48,13034,1863],{"class":482},[48,13036,13037],{"class":471},"-9999",[48,13039,12866],{"class":500},[48,13041,1149],{"class":482},[48,13043,13044],{"class":50,"line":2060},[48,13045,266],{"class":482},[48,13047,13048,13050,13053,13056,13058,13060],{"class":50,"line":2073},[48,13049,22],{"class":8849},[48,13051,13052],{"class":482},"[",[48,13054,13055],{"class":467},"href",[48,13057,4007],{"class":500},[48,13059,12561],{"class":475},[48,13061,13062],{"class":482},"] {\n",[48,13064,13065,13068,13070,13073,13075,13078],{"class":50,"line":2081},[48,13066,13067],{"class":471}," background",[48,13069,1863],{"class":482},[48,13071,13072],{"class":471},"url",[48,13074,483],{"class":482},[48,13076,13077],{"class":5911},"home.png",[48,13079,495],{"class":482},[48,13081,13082,13084,13086,13088,13090],{"class":50,"line":2092},[48,13083,12873],{"class":471},[48,13085,1863],{"class":482},[48,13087,1899],{"class":471},[48,13089,12866],{"class":500},[48,13091,1149],{"class":482},[48,13093,13094],{"class":50,"line":2098},[48,13095,266],{"class":482},[48,13097,13098,13100,13102,13104,13106,13108,13111,13114],{"class":50,"line":2106},[48,13099,22],{"class":8849},[48,13101,13052],{"class":482},[48,13103,13055],{"class":467},[48,13105,4007],{"class":500},[48,13107,12561],{"class":475},[48,13109,13110],{"class":482},"]",[48,13112,13113],{"class":467},".current",[48,13115,5901],{"class":482},[48,13117,13118,13120,13122,13124,13126,13129],{"class":50,"line":2112},[48,13119,13067],{"class":471},[48,13121,1863],{"class":482},[48,13123,13072],{"class":471},[48,13125,483],{"class":482},[48,13127,13128],{"class":5911},"home-current.png",[48,13130,495],{"class":482},[48,13132,13133],{"class":50,"line":2117},[48,13134,266],{"class":482},[48,13136,13137,13139,13141,13143,13145,13147],{"class":50,"line":2129},[48,13138,22],{"class":8849},[48,13140,13052],{"class":482},[48,13142,13055],{"class":467},[48,13144,4007],{"class":500},[48,13146,12590],{"class":475},[48,13148,13062],{"class":482},[48,13150,13151,13153,13155,13157,13159,13162],{"class":50,"line":2150},[48,13152,13067],{"class":471},[48,13154,1863],{"class":482},[48,13156,13072],{"class":471},[48,13158,483],{"class":482},[48,13160,13161],{"class":5911},"new.png",[48,13163,495],{"class":482},[48,13165,13166,13168,13170,13172,13174],{"class":50,"line":2161},[48,13167,12873],{"class":471},[48,13169,1863],{"class":482},[48,13171,13011],{"class":471},[48,13173,12866],{"class":500},[48,13175,1149],{"class":482},[48,13177,13178],{"class":50,"line":2173},[48,13179,266],{"class":482},[48,13181,13182,13184,13186,13188,13190,13192,13194,13196],{"class":50,"line":2184},[48,13183,22],{"class":8849},[48,13185,13052],{"class":482},[48,13187,13055],{"class":467},[48,13189,4007],{"class":500},[48,13191,12590],{"class":475},[48,13193,13110],{"class":482},[48,13195,13113],{"class":467},[48,13197,5901],{"class":482},[48,13199,13200,13202,13204,13206,13208,13211],{"class":50,"line":2195},[48,13201,13067],{"class":471},[48,13203,1863],{"class":482},[48,13205,13072],{"class":471},[48,13207,483],{"class":482},[48,13209,13210],{"class":5911},"new-current.png",[48,13212,495],{"class":482},[48,13214,13215],{"class":50,"line":2207},[48,13216,266],{"class":482},[48,13218,13219,13221,13223,13225,13227,13229],{"class":50,"line":2215},[48,13220,22],{"class":8849},[48,13222,13052],{"class":482},[48,13224,13055],{"class":467},[48,13226,4007],{"class":500},[48,13228,12618],{"class":475},[48,13230,13062],{"class":482},[48,13232,13233,13235,13237,13239,13241,13244],{"class":50,"line":2222},[48,13234,13067],{"class":471},[48,13236,1863],{"class":482},[48,13238,13072],{"class":471},[48,13240,483],{"class":482},[48,13242,13243],{"class":5911},"favorites.png",[48,13245,495],{"class":482},[48,13247,13248,13250,13252,13255,13257],{"class":50,"line":2227},[48,13249,12873],{"class":471},[48,13251,1863],{"class":482},[48,13253,13254],{"class":471},"160",[48,13256,12866],{"class":500},[48,13258,1149],{"class":482},[48,13260,13261],{"class":50,"line":2232},[48,13262,266],{"class":482},[48,13264,13265,13267,13269,13271,13273,13275,13277,13279],{"class":50,"line":2244},[48,13266,22],{"class":8849},[48,13268,13052],{"class":482},[48,13270,13055],{"class":467},[48,13272,4007],{"class":500},[48,13274,12618],{"class":475},[48,13276,13110],{"class":482},[48,13278,13113],{"class":467},[48,13280,5901],{"class":482},[48,13282,13283,13285,13287,13289,13291,13294],{"class":50,"line":2260},[48,13284,13067],{"class":471},[48,13286,1863],{"class":482},[48,13288,13072],{"class":471},[48,13290,483],{"class":482},[48,13292,13293],{"class":5911},"favorites-current.png",[48,13295,495],{"class":482},[48,13297,13298],{"class":50,"line":2271},[48,13299,266],{"class":482},[48,13301,13302,13304,13306,13308,13310,13312],{"class":50,"line":2282},[48,13303,22],{"class":8849},[48,13305,13052],{"class":482},[48,13307,13055],{"class":467},[48,13309,4007],{"class":500},[48,13311,12646],{"class":475},[48,13313,13062],{"class":482},[48,13315,13316,13318,13320,13322,13324,13327],{"class":50,"line":2294},[48,13317,13067],{"class":471},[48,13319,1863],{"class":482},[48,13321,13072],{"class":471},[48,13323,483],{"class":482},[48,13325,13326],{"class":5911},"more.png",[48,13328,495],{"class":482},[48,13330,13331,13333,13335,13338,13340],{"class":50,"line":2305},[48,13332,12873],{"class":471},[48,13334,1863],{"class":482},[48,13336,13337],{"class":471},"240",[48,13339,12866],{"class":500},[48,13341,1149],{"class":482},[48,13343,13344],{"class":50,"line":2316},[48,13345,266],{"class":482},[48,13347,13348,13350,13352,13354,13356,13358],{"class":50,"line":2323},[48,13349,22],{"class":8849},[48,13351,13052],{"class":482},[48,13353,13055],{"class":467},[48,13355,4007],{"class":500},[48,13357,12674],{"class":475},[48,13359,13062],{"class":482},[48,13361,13362,13364,13366,13368,13370,13373],{"class":50,"line":2330},[48,13363,13067],{"class":471},[48,13365,1863],{"class":482},[48,13367,13072],{"class":471},[48,13369,483],{"class":482},[48,13371,13372],{"class":5911},"info.png",[48,13374,495],{"class":482},[48,13376,13377,13379,13381,13383],{"class":50,"line":2335},[48,13378,12847],{"class":471},[48,13380,1863],{"class":482},[48,13382,12852],{"class":471},[48,13384,1149],{"class":482},[48,13386,13387,13389,13391,13393,13395],{"class":50,"line":2340},[48,13388,12886],{"class":471},[48,13390,1863],{"class":482},[48,13392,12998],{"class":471},[48,13394,12866],{"class":500},[48,13396,1149],{"class":482},[48,13398,13399,13401,13403,13405,13407],{"class":50,"line":2352},[48,13400,12993],{"class":471},[48,13402,1863],{"class":482},[48,13404,12998],{"class":471},[48,13406,12866],{"class":500},[48,13408,1149],{"class":482},[48,13410,13411,13414,13416,13419,13421],{"class":50,"line":2377},[48,13412,13413],{"class":471}," bottom",[48,13415,1863],{"class":482},[48,13417,13418],{"class":471},"16",[48,13420,12866],{"class":500},[48,13422,1149],{"class":482},[48,13424,13425,13428,13430,13432,13434],{"class":50,"line":2389},[48,13426,13427],{"class":471}," right",[48,13429,1863],{"class":482},[48,13431,13418],{"class":471},[48,13433,12866],{"class":500},[48,13435,1149],{"class":482},[48,13437,13438],{"class":50,"line":2400},[48,13439,266],{"class":482},[48,13441,13442],{"class":50,"line":2411},[48,13443,13444],{"class":3320},"/* finally position the views themselves */\n",[48,13446,13447,13449,13452],{"class":50,"line":2422},[48,13448,9019],{"class":8849},[48,13450,13451],{"class":467},".view",[48,13453,5901],{"class":482},[48,13455,13456,13458,13460,13462],{"class":50,"line":2434},[48,13457,12847],{"class":471},[48,13459,1863],{"class":482},[48,13461,12951],{"class":471},[48,13463,1149],{"class":482},[48,13465,13466,13468,13470,13472,13474],{"class":50,"line":2441},[48,13467,12859],{"class":471},[48,13469,1863],{"class":482},[48,13471,12998],{"class":471},[48,13473,12866],{"class":500},[48,13475,1149],{"class":482},[48,13477,13478,13480,13482,13484,13486],{"class":50,"line":2454},[48,13479,12873],{"class":471},[48,13481,1863],{"class":482},[48,13483,1899],{"class":471},[48,13485,12866],{"class":500},[48,13487,1149],{"class":482},[48,13489,13490,13492,13494,13496,13498],{"class":50,"line":2467},[48,13491,12886],{"class":471},[48,13493,1863],{"class":482},[48,13495,12891],{"class":471},[48,13497,12866],{"class":500},[48,13499,1149],{"class":482},[48,13501,13502,13504,13506,13509,13511],{"class":50,"line":2478},[48,13503,12993],{"class":471},[48,13505,1863],{"class":482},[48,13507,13508],{"class":471},"396",[48,13510,12866],{"class":500},[48,13512,1149],{"class":482},[48,13514,13515],{"class":50,"line":2483},[48,13516,266],{"class":482},[18,13518,13519],{},"This can act as a simple framework for an application with multiple views. Now we still need to make our navigation\ncontroller to switch between the different views. jQuery to the rescue — a small snippet initializes our view hierarchy\nand provides switching capabilities:",[38,13521,13523],{"className":11361,"code":13522,"language":11363,"meta":43,"style":43},"$(document).ready(function () {\n var navitems = $(\"#navigation li a\");\n navitems.click(function () {\n navitems.removeClass(\"current\");\n var ref = $(this).addClass(\"current\").attr(\"href\");\n /* hide the other views, show the one navigated to\n and trigger a custom event */\n $(\"div.view\").hide();\n $(ref).show().trigger(\"becameActive\");\n });\n $(\"div.view\").hide();\n $(\"div.view.current\").show().trigger(\"becameActive\");\n});\n",[45,13524,13525,13543,13563,13577,13591,13628,13633,13638,13655,13677,13682,13697,13720],{"__ignoreMap":43},[48,13526,13527,13530,13533,13536,13538,13540],{"class":50,"line":51},[48,13528,13529],{"class":467},"$",[48,13531,13532],{"class":482},"(document).",[48,13534,13535],{"class":467},"ready",[48,13537,483],{"class":482},[48,13539,5895],{"class":500},[48,13541,13542],{"class":482}," () {\n",[48,13544,13545,13548,13551,13553,13556,13558,13561],{"class":50,"line":57},[48,13546,13547],{"class":500}," var",[48,13549,13550],{"class":482}," navitems ",[48,13552,4007],{"class":500},[48,13554,13555],{"class":467}," $",[48,13557,483],{"class":482},[48,13559,13560],{"class":475},"\"#navigation li a\"",[48,13562,495],{"class":482},[48,13564,13565,13568,13571,13573,13575],{"class":50,"line":63},[48,13566,13567],{"class":482}," navitems.",[48,13569,13570],{"class":467},"click",[48,13572,483],{"class":482},[48,13574,5895],{"class":500},[48,13576,13542],{"class":482},[48,13578,13579,13582,13585,13587,13589],{"class":50,"line":69},[48,13580,13581],{"class":482}," navitems.",[48,13583,13584],{"class":467},"removeClass",[48,13586,483],{"class":482},[48,13588,12553],{"class":475},[48,13590,495],{"class":482},[48,13592,13593,13596,13599,13601,13603,13605,13607,13609,13612,13614,13616,13618,13621,13623,13626],{"class":50,"line":75},[48,13594,13595],{"class":500}," var",[48,13597,13598],{"class":482}," ref ",[48,13600,4007],{"class":500},[48,13602,13555],{"class":467},[48,13604,483],{"class":482},[48,13606,10725],{"class":471},[48,13608,7412],{"class":482},[48,13610,13611],{"class":467},"addClass",[48,13613,483],{"class":482},[48,13615,12553],{"class":475},[48,13617,7412],{"class":482},[48,13619,13620],{"class":467},"attr",[48,13622,483],{"class":482},[48,13624,13625],{"class":475},"\"href\"",[48,13627,495],{"class":482},[48,13629,13630],{"class":50,"line":81},[48,13631,13632],{"class":3320}," /* hide the other views, show the one navigated to\n",[48,13634,13635],{"class":50,"line":124},[48,13636,13637],{"class":3320}," and trigger a custom event */\n",[48,13639,13640,13643,13645,13648,13650,13653],{"class":50,"line":129},[48,13641,13642],{"class":467}," $",[48,13644,483],{"class":482},[48,13646,13647],{"class":475},"\"div.view\"",[48,13649,7412],{"class":482},[48,13651,13652],{"class":467},"hide",[48,13654,11389],{"class":482},[48,13656,13657,13659,13662,13665,13667,13670,13672,13675],{"class":50,"line":204},[48,13658,13642],{"class":467},[48,13660,13661],{"class":482},"(ref).",[48,13663,13664],{"class":467},"show",[48,13666,11407],{"class":482},[48,13668,13669],{"class":467},"trigger",[48,13671,483],{"class":482},[48,13673,13674],{"class":475},"\"becameActive\"",[48,13676,495],{"class":482},[48,13678,13679],{"class":50,"line":210},[48,13680,13681],{"class":482}," });\n",[48,13683,13684,13687,13689,13691,13693,13695],{"class":50,"line":216},[48,13685,13686],{"class":467}," $",[48,13688,483],{"class":482},[48,13690,13647],{"class":475},[48,13692,7412],{"class":482},[48,13694,13652],{"class":467},[48,13696,11389],{"class":482},[48,13698,13699,13701,13703,13706,13708,13710,13712,13714,13716,13718],{"class":50,"line":357},[48,13700,13686],{"class":467},[48,13702,483],{"class":482},[48,13704,13705],{"class":475},"\"div.view.current\"",[48,13707,7412],{"class":482},[48,13709,13664],{"class":467},[48,13711,11407],{"class":482},[48,13713,13669],{"class":467},[48,13715,483],{"class":482},[48,13717,13674],{"class":475},[48,13719,495],{"class":482},[48,13721,13722],{"class":50,"line":363},[48,13723,13724],{"class":482},"});\n",[18,13726,13727,13728,13731,13732,13735],{},"With custom events like ",[45,13729,13730],{},"becameActive"," it is easy to create new callbacks that are invoked when state changes occur — in\nthis example when switching the active view through our navigation controller. For brevity we will omit a elaborate\nexample of controller code, but if for example you want code to run when switching to the ",[45,13733,13734],{},"#new"," view, you could simply\nwrite:",[38,13737,13739],{"className":11361,"code":13738,"language":11363,"meta":43,"style":43},"$(\"#new\").bind(\"becameActive\", function (event) {\n $(event.target).doSomething();\n});\n",[45,13740,13741,13771,13783],{"__ignoreMap":43},[48,13742,13743,13745,13747,13749,13751,13754,13756,13758,13760,13762,13765,13768],{"class":50,"line":51},[48,13744,13529],{"class":467},[48,13746,483],{"class":482},[48,13748,12590],{"class":475},[48,13750,7412],{"class":482},[48,13752,13753],{"class":467},"bind",[48,13755,483],{"class":482},[48,13757,13674],{"class":475},[48,13759,744],{"class":482},[48,13761,5895],{"class":500},[48,13763,13764],{"class":482}," (",[48,13766,13767],{"class":5911},"event",[48,13769,13770],{"class":482},") {\n",[48,13772,13773,13775,13778,13781],{"class":50,"line":57},[48,13774,13686],{"class":467},[48,13776,13777],{"class":482},"(event.target).",[48,13779,13780],{"class":467},"doSomething",[48,13782,11389],{"class":482},[48,13784,13785],{"class":50,"line":63},[48,13786,13724],{"class":482},[18,13788,13789],{},"So what is still missing now? We still haven’t provided a data model to operate on. While data provided via web services\nis not a big deal by the use of JSON-APIs, we would certainly want to have a local data storage too. For quite some\ntime already, web engines provide a local SQLite-based storage system. On application initialization we can simply\nsetup some tables to hold our data and send statements from anywhere in the application later on:",[38,13791,13793],{"className":11361,"code":13792,"language":11363,"meta":43,"style":43},"// open a database, providing it's name, version,\n// maximum size and display name\nvar database = openDatabase(\"Example\", \"1.0\", 1048576, \"Example Database\");\ndatabase.transaction(function (transaction) {\n transaction.executeSQL(\n \"CREATE TABLE IF NOT EXISTS data\" +\n \"(id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,\" +\n \"... more declarations ...);\",\n );\n});\n// other statements are issued the same way\ndatabase.transaction(function (transaction) {\n transaction.executeSQL(\"SELECT * FROM data;\", function (transaction, result) {\n // do something with 'result'\n });\n});\n",[45,13794,13795,13800,13805,13839,13857,13867,13875,13882,13889,13894,13898,13903,13919,13945,13950,13954],{"__ignoreMap":43},[48,13796,13797],{"class":50,"line":51},[48,13798,13799],{"class":3320},"// open a database, providing it's name, version,\n",[48,13801,13802],{"class":50,"line":57},[48,13803,13804],{"class":3320},"// maximum size and display name\n",[48,13806,13807,13809,13812,13814,13817,13819,13822,13824,13827,13829,13832,13834,13837],{"class":50,"line":63},[48,13808,11375],{"class":500},[48,13810,13811],{"class":482}," database ",[48,13813,4007],{"class":500},[48,13815,13816],{"class":467}," openDatabase",[48,13818,483],{"class":482},[48,13820,13821],{"class":475},"\"Example\"",[48,13823,744],{"class":482},[48,13825,13826],{"class":475},"\"1.0\"",[48,13828,744],{"class":482},[48,13830,13831],{"class":471},"1048576",[48,13833,744],{"class":482},[48,13835,13836],{"class":475},"\"Example Database\"",[48,13838,495],{"class":482},[48,13840,13841,13844,13847,13849,13851,13853,13855],{"class":50,"line":69},[48,13842,13843],{"class":482},"database.",[48,13845,13846],{"class":467},"transaction",[48,13848,483],{"class":482},[48,13850,5895],{"class":500},[48,13852,13764],{"class":482},[48,13854,13846],{"class":5911},[48,13856,13770],{"class":482},[48,13858,13859,13862,13865],{"class":50,"line":75},[48,13860,13861],{"class":482}," transaction.",[48,13863,13864],{"class":467},"executeSQL",[48,13866,556],{"class":482},[48,13868,13869,13872],{"class":50,"line":81},[48,13870,13871],{"class":475}," \"CREATE TABLE IF NOT EXISTS data\"",[48,13873,13874],{"class":500}," +\n",[48,13876,13877,13880],{"class":50,"line":124},[48,13878,13879],{"class":475}," \"(id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,\"",[48,13881,13874],{"class":500},[48,13883,13884,13887],{"class":50,"line":129},[48,13885,13886],{"class":475}," \"... more declarations ...);\"",[48,13888,1869],{"class":482},[48,13890,13891],{"class":50,"line":204},[48,13892,13893],{"class":482}," );\n",[48,13895,13896],{"class":50,"line":210},[48,13897,13724],{"class":482},[48,13899,13900],{"class":50,"line":216},[48,13901,13902],{"class":3320},"// other statements are issued the same way\n",[48,13904,13905,13907,13909,13911,13913,13915,13917],{"class":50,"line":357},[48,13906,13843],{"class":482},[48,13908,13846],{"class":467},[48,13910,483],{"class":482},[48,13912,5895],{"class":500},[48,13914,13764],{"class":482},[48,13916,13846],{"class":5911},[48,13918,13770],{"class":482},[48,13920,13921,13923,13925,13927,13930,13932,13934,13936,13938,13940,13943],{"class":50,"line":363},[48,13922,13861],{"class":482},[48,13924,13864],{"class":467},[48,13926,483],{"class":482},[48,13928,13929],{"class":475},"\"SELECT * FROM data;\"",[48,13931,744],{"class":482},[48,13933,5895],{"class":500},[48,13935,13764],{"class":482},[48,13937,13846],{"class":5911},[48,13939,744],{"class":482},[48,13941,13942],{"class":5911},"result",[48,13944,13770],{"class":482},[48,13946,13947],{"class":50,"line":369},[48,13948,13949],{"class":3320}," // do something with 'result'\n",[48,13951,13952],{"class":50,"line":1978},[48,13953,13681],{"class":482},[48,13955,13956],{"class":50,"line":1991},[48,13957,13724],{"class":482},[1822,13959,13961],{"id":13960},"putting-it-all-together","Putting it all together",[18,13963,13964,13965,10653,13968],{},"A question remains: ",[275,13966,13967],{},"when this is the basic skeleton of an application, how do i put this on an actual\ndevice?",[1195,13969],{"alt":43,"src":13970},"https://media.synyx.de/uploads//2010/08/jquery-html-css-example-e1281692547570.png",[18,13972,13973],{},"Example in Simulator",[18,13975,13976,13977,13982],{},"While this could be simply served by a web server to a mobile device, the main reason for developing applications\ninstead of websites is the ability to expose them through an application store (e.g. Apples AppStore or the Android\nMarket). We won’t go into much detail here now — this is something to be discussed in the following blog posts in this\nseries — a simple answer is provided by a small framework\nnamed ",[22,13978,13981],{"href":13979,"rel":13980},"https://web.archive.org/web/20210302121558/https://phonegap.com/",[26],"PhoneGap",". PhoneGap provides the developer with\na cross-platform deployment environment, which makes use of the web engines on the different mobile devices. It\nbasically is a fullscreen web browser view, without any UI elements and other decorations but with added support for\naccessing hardware features of the actual device. Next to the already mentioned features, which are part of the modern\nweb engines, it gives access to features like cameras, accelerometer or access to the native phonebook of your mobile\nphone. You simply drop all your HTML, CSS & JavaScript into a PhoneGap application package which is built for your\nparticular device and are ready to deploy through any application store or similar channel you like.",[1822,13984,13986],{"id":13985},"whats-next","What’s next?",[18,13988,13989],{},"So developing mobile applications with HTML, CSS & JavaScript is actually easy, isn’t it? Well, with the basic ideas\noutlined above it is — until it isn’t anymore. In the parts of this series which are still to come, we will answer some\nquestions, that might be crucial to your development cycle:",[868,13991,13992,13995,13998,14001],{},[871,13993,13994],{},"What problems might arise with JavaScript as a development language? Is there enough tool support for ease of\ndevelopment?",[871,13996,13997],{},"What do i have to program from scratch and which frameworks are available, that erase my need of boilerplate code?",[871,13999,14000],{},"What about the performance, for example when sophisticated animations are desired?",[871,14002,14003],{},"Is my application really cross-platform, when using these technologies, and does it behave exactly the same on every\ndevice?",[18,14005,14006],{},"Stay tuned for more.",[394,14008,14009],{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":43,"searchDepth":57,"depth":57,"links":14011},[14012,14013,14014,14015],{"id":12499,"depth":57,"text":12500},{"id":12506,"depth":57,"text":12507},{"id":13960,"depth":57,"text":13961},{"id":13985,"depth":57,"text":13986},[6671,406],"2010-08-13T12:17:29","Once in a while a demand for fast development of a mobile application for several platforms at once comes up. Your team\\nof developers might be small or knowledge about the different Software Development Kits (SDKs) involved is scarce. But\\nhey, perhaps you know how to write HTML & CSS and are also experienced with JavaScript. With the advent of the Apple\\niPhone and briefly afterwards the Android line of phones, mobile devices started to support a lot of modern HTML & CSS\\nfeatures. The progressing “applification” of the WWW further boosted the development of fast JavaScript engines. And\\nwith the recent addition of multitouch, geolocation and fast CSS3 animation support, the mobile browser has become a new\\ndeployment target for mobile applications. That’s the theory at least. In this series of articles we will provide an\\noverview on the technologies involved, available frameworks and the approaches taken to bring your application to\\nseveral mobile platforms at once.","https://synyx.de/blog/on-cross-device-mobile-development-part-1/",{},"/blog/on-cross-device-mobile-development-part-1",{"title":12483,"description":14023},"Once in a while a demand for fast development of a mobile application for several platforms at once comes up. Your team\nof developers might be small or knowledge about the different Software Development Kits (SDKs) involved is scarce. But\nhey, perhaps you know how to write HTML & CSS and are also experienced with JavaScript. With the advent of the Apple\niPhone and briefly afterwards the Android line of phones, mobile devices started to support a lot of modern HTML & CSS\nfeatures. The progressing “applification” of the WWW further boosted the development of fast JavaScript engines. And\nwith the recent addition of multitouch, geolocation and fast CSS3 animation support, the mobile browser has become a new\ndeployment target for mobile applications. That’s the theory at least. In this series of articles we will provide an\noverview on the technologies involved, available frameworks and the approaches taken to bring your application to\nseveral mobile platforms at once.","blog/on-cross-device-mobile-development-part-1",[7380,12828,6842,11603,11363],"Once in a while a demand for fast development of a mobile application for several platforms at once comes up. Your team of developers might be small or knowledge about…","yJGRhWop8a6UwlupSTSgkogMBabnFF7yVveZDcNWM8Y",{"id":14029,"title":14030,"author":14031,"body":14033,"category":14138,"date":14139,"description":14140,"extension":409,"link":14141,"meta":14142,"navigation":177,"path":14143,"seo":14144,"slug":14037,"stem":14145,"tags":14146,"teaser":14148,"__hash__":14149},"blog/blog/debugging-on-your-n900.md","Debugging on your N900",[14032],"gast",{"type":11,"value":14034,"toc":14136},[14035,14038,14041,14044,14047,14050,14057,14060,14063,14066,14069,14072,14075,14080,14083,14086,14089,14092,14095,14098,14101,14104,14107,14110,14113,14116,14119,14125,14128],[14,14036,14030],{"id":14037},"debugging-on-your-n900",[18,14039,14040],{},"During development I realized that many device specific features are not available in the emulator. So I decided to find\na way to debug software in realtime on the device. First of all I had to upgrade my Device to version PR 1.2. So my\nSDK and the device libraries had the same version numbers.",[18,14042,14043],{},"I had to install the „maemo pc connectivity“ Package on my N900 and on my host pc.",[18,14045,14046],{},"First I enabled the developer repository on my N900:",[18,14048,14049],{},"`catalog name: extras-devel",[18,14051,14052,14053],{},"web address: ",[22,14054,14055],{"href":14055,"rel":14056},"http://repository.maemo.org/extras-devel",[26],[18,14058,14059],{},"distribution: fremantle",[18,14061,14062],{},"components: free non-free`",[18,14064,14065],{},"in a shell on the N900:",[18,14067,14068],{},"`sudo gainroot",[18,14070,14071],{},"apt-get install maemo-pc-connectivity gdb`",[18,14073,14074],{},"On my Host System I added the repository:",[18,14076,14077],{},[45,14078,14079],{},"deb http://pc-connectivity.garage.maemo.org/repository intrepid main",[18,14081,14082],{},"and installed host-pc-connectivity by typing:",[18,14084,14085],{},"`sudo apt-get update",[18,14087,14088],{},"sudo apt-get instsall host-pc-connectivity`",[18,14090,14091],{},"I had a new usb device with an IP address 192.168.2.14.",[18,14093,14094],{},"On my N900 I configured in the system-settings->pc connectivity manager the “default” environment:",[18,14096,14097],{},"`connection type: usb",[18,14099,14100],{},"ipadress: 192.168.2.15",[18,14102,14103],{},"gateway: 192.168.2.14`",[18,14105,14106],{},"After applying and saving the changes the N900 was ready. I connected it to my pc and opened an ssh shell to the device.\nI used the username and password I have been asked for while installation on the n900.",[18,14108,14109],{},"In ESBox i had to open the Debug Configurations Dialog and created a new Maemo Remote Application. Most points should be\nclear. The point Download is interesting: I used",[18,14111,14112],{},"`Download Method: ssh",[18,14114,14115],{},"RemoteConnection: 192.168.2.15`",[18,14117,14118],{},"Verify the target path. It must be a path that is writeable by the user you have used for the ssh session above. Klick\non „Edit“->the link SSH panel and configure the destination folder if necessary.",[18,14120,14121],{},[1195,14122],{"alt":14123,"src":14124},"\"Debug Configuration - Download Method\"","https://media.synyx.de/uploads//2010/07/maemo.jpeg",[18,14126,14127],{},"Debug Configuration - Download Method",[18,14129,14130,14131,14135],{},"That was all. The complete documentation of “pc connectivity” can be found here ",[22,14132,14133],{"href":14133,"rel":14134},"http://pc",[26],"\n-connectivity.garage.maemo.org/2nd_edition/index.html. This documentation describes howto use certificates to avoid\npassword fields during the launch process. This is not necessary, simply type the username and password once and check\nremember.",{"title":43,"searchDepth":57,"depth":57,"links":14137},[],[6671,406],"2010-07-23T18:16:02","During development I realized that many device specific features are not available in the emulator. So I decided to find\\na way to debug software in realtime on the device. First of all I had to upgrade my Device to version PR 1.2. So my\\nSDK and the device libraries had the same version numbers.","https://synyx.de/blog/debugging-on-your-n900/",{},"/blog/debugging-on-your-n900",{"title":14030,"description":14040},"blog/debugging-on-your-n900",[14147],"maemo-5","During development I realized that many device specific features are not available in the emulator. So I decided to find a way to debug software in realtime on the device.…","y6BlJ3xFq2irGQlpko633kqQHlvK_JHnCYrYKIXufD4",{"id":14151,"title":14152,"author":14153,"body":14154,"category":14236,"date":14237,"description":14238,"extension":409,"link":14239,"meta":14240,"navigation":177,"path":14241,"seo":14242,"slug":14158,"stem":14244,"tags":14245,"teaser":14247,"__hash__":14248},"blog/blog/split-nsstring-by-characters.md","Split NSString by characters",[11319],{"type":11,"value":14155,"toc":14234},[14156,14159,14181,14190,14229,14232],[14,14157,14152],{"id":14158},"split-nsstring-by-characters",[18,14160,14161,14162,14169,14170,14172,14173,14180],{},"Have you ever pondered on the ",[22,14163,14166],{"href":14164,"rel":14165},"http://developer.apple.com/iphone/library/documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/Reference/NSString.html",[26],[275,14167,14168],{},"NSString","\nclass reference and you were overwhelmed by the sheer amount of methods? I certainly have and due to the endless\npossibilities those methods give you, I just couldn’t figure out how to split an ",[275,14171,14168],{}," by characters and create a ",[22,14174,14177],{"href":14175,"rel":14176},"http://developer.apple.com/iphone/library/documentation/Cocoa/Reference/Foundation/Classes/NSArray_Class/NSArray.html",[26],[275,14178,14179],{},"NSArray","\nof those characters.",[18,14182,14183,14184,14189],{},"Blame it on my lack of creativity or on the fact that I’m not a C buff. However, thanks to my creative Googling, I came\nacross ",[22,14185,14188],{"href":14186,"rel":14187},"http://www.idev101.com/code/Objective-C/Strings/split.html",[26],"this site",", which shows you how to do it:",[38,14191,14193],{"className":12276,"code":14192,"language":12278,"meta":43,"style":43},"NSMutableArray *characters =\n[[NSMutableArray alloc] initWithCapacity:[myString length]];\nfor (int i=0; i \u003C [myString length]; i++) {\n NSString *ichar =\n[NSString stringWithFormat:@\"%c\", [myString characterAtIndex:i]];\n [characters addObject:ichar];\n}\n",[45,14194,14195,14200,14205,14210,14215,14220,14225],{"__ignoreMap":43},[48,14196,14197],{"class":50,"line":51},[48,14198,14199],{},"NSMutableArray *characters =\n",[48,14201,14202],{"class":50,"line":57},[48,14203,14204],{},"[[NSMutableArray alloc] initWithCapacity:[myString length]];\n",[48,14206,14207],{"class":50,"line":63},[48,14208,14209],{},"for (int i=0; i \u003C [myString length]; i++) {\n",[48,14211,14212],{"class":50,"line":69},[48,14213,14214],{}," NSString *ichar =\n",[48,14216,14217],{"class":50,"line":75},[48,14218,14219],{},"[NSString stringWithFormat:@\"%c\", [myString characterAtIndex:i]];\n",[48,14221,14222],{"class":50,"line":81},[48,14223,14224],{}," [characters addObject:ichar];\n",[48,14226,14227],{"class":50,"line":124},[48,14228,266],{},[18,14230,14231],{},"Although Apple’s documentation is extensive, you sometimes need a little hint or guidance to achieve your goal.",[394,14233,396],{},{"title":43,"searchDepth":57,"depth":57,"links":14235},[],[6671,406],"2010-07-11T11:33:48","Have you ever pondered on the NSString\\nclass reference and you were overwhelmed by the sheer amount of methods? I certainly have and due to the endless\\npossibilities those methods give you, I just couldn’t figure out how to split an NSString by characters and create a NSArray\\nof those characters.","https://synyx.de/blog/split-nsstring-by-characters/",{},"/blog/split-nsstring-by-characters",{"title":14152,"description":14243},"Have you ever pondered on the NSString\nclass reference and you were overwhelmed by the sheer amount of methods? I certainly have and due to the endless\npossibilities those methods give you, I just couldn’t figure out how to split an NSString by characters and create a NSArray\nof those characters.","blog/split-nsstring-by-characters",[14246],"objective-c","Have you ever pondered on the NSString class reference and you were overwhelmed by the sheer amount of methods? I certainly have and due to the endless possibilities those methods…","ilLoGswdB2P1Kne210H_GnKvBa-ixSkTUAhBMe3b3TY",{"id":14250,"title":14251,"author":14252,"body":14253,"category":14493,"date":14494,"description":14495,"extension":409,"link":14496,"meta":14497,"navigation":177,"path":14498,"seo":14499,"slug":14257,"stem":14501,"tags":14502,"teaser":14505,"__hash__":14506},"blog/blog/ui-prototyping-iphone-apps.md","UI Prototyping iPhone Apps",[11319],{"type":11,"value":14254,"toc":14491},[14255,14258,14266,14269,14278,14289,14342,14352,14475,14478,14481,14489],[14,14256,14251],{"id":14257},"ui-prototyping-iphone-apps",[18,14259,14260,14261,14265],{},"Before ",[22,14262,14264],{"href":11330,"rel":14263},[26],"flying off to WWDC"," last month, I watched a whole bunch of sessions\nfrom 2009. Among others a session on “Prototyping iPhone User Interfaces” by Bret Victor. If you haven’t watched it and\nyou’ve got access to the WWDC videos – stop right here and watch the video!",[18,14267,14268],{},"In his session, Bret shows how to prototype an interface only by using with screenshots! It’s amazing that a simple\nscreenshot on the device can show you so much more than by just looking at it in a document or print out. It inspired me\nto use his framework and the whole process for our own development.",[18,14270,14271,14272,14277],{},"Unfortunately, the code for the session isn’t available and neither Bret nor the frameworks evangelist mentioned in the\npresentation got back to me about the code. After some digging, I\nfound ",[22,14273,14276],{"href":14274,"rel":14275},"https://web.archive.org/web/20130723100234/http://www.fruitstandsoftware.com:80/blog/2009/07/uiview-manipulation-made-easier-with-a-category/",[26],"Michael Fey’s blog",",\nwho was able to successfully reverse engineer the missing parts, which were not shown in the presentation.",[18,14279,14280,14281,14284,14285,14288],{},"Michael’s ",[275,14282,14283],{},"UIViewAdditions"," basically allow easy access to frame properties and give you a neat init method, which adds\nthe passed ",[275,14286,14287],{},"UIView"," as a parent:",[38,14290,14292],{"className":12276,"code":14291,"language":12278,"meta":43,"style":43},"- (id)initWithParent:(UIView *)parent {\n self = [self initWithFrame:CGRectZero];\n if (!self)\n return nil;\n [parent addSubview:self];\n return self;\n}\n+ (id) viewWithParent:(UIView *)parent {\n return [[[self alloc] initWithParent:parent] autorelease];\n}\n",[45,14293,14294,14299,14304,14309,14314,14319,14324,14328,14333,14338],{"__ignoreMap":43},[48,14295,14296],{"class":50,"line":51},[48,14297,14298],{},"- (id)initWithParent:(UIView *)parent {\n",[48,14300,14301],{"class":50,"line":57},[48,14302,14303],{}," self = [self initWithFrame:CGRectZero];\n",[48,14305,14306],{"class":50,"line":63},[48,14307,14308],{}," if (!self)\n",[48,14310,14311],{"class":50,"line":69},[48,14312,14313],{}," return nil;\n",[48,14315,14316],{"class":50,"line":75},[48,14317,14318],{}," [parent addSubview:self];\n",[48,14320,14321],{"class":50,"line":81},[48,14322,14323],{}," return self;\n",[48,14325,14326],{"class":50,"line":124},[48,14327,266],{},[48,14329,14330],{"class":50,"line":129},[48,14331,14332],{},"+ (id) viewWithParent:(UIView *)parent {\n",[48,14334,14335],{"class":50,"line":204},[48,14336,14337],{}," return [[[self alloc] initWithParent:parent] autorelease];\n",[48,14339,14340],{"class":50,"line":210},[48,14341,266],{},[18,14343,14344,14345,14348,14349,14351],{},"There wasn’t much left to do for me. I only coded the class ",[275,14346,14347],{},"Root",", which is the parent of all ",[275,14350,11555],{}," instances\nused in the prototype. It provides a couple of methods to slide images back and forth:",[38,14353,14355],{"className":12276,"code":14354,"language":12278,"meta":43,"style":43},"@synthesize pageIndex = _pageIndex;\n- (id) initWithParent:(UIView *)parent {\n self = [super initWithParent:parent];\n if (self == nil) {\n return nil;\n }\n self.userInteractionEnabled = YES;\n self.size = self.window.size;\n [[UIImageView viewWithParent:self] setImageWithName:@\"dailies\"];\n self.pageIndex = 0;\n return self;\n}\n- (void)setPageIndex:(int)index {\n if (index \u003C 0 || index >= [self.subviews count]) {\n return;\n }\n _pageIndex = index;\n for (int i = 0; i \u003C [self.subviews count]; i++) {\n UIImageView *page = [self.subviews objectAtIndex:i];\n page.x = (i \u003C _pageIndex) ? -self.width : (i > _pageIndex) ? self.width : 0;\n }\n}\n- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {\n self.pageIndex++;\n}\n",[45,14356,14357,14362,14367,14372,14377,14381,14386,14391,14396,14401,14406,14410,14414,14419,14424,14429,14433,14438,14443,14448,14453,14457,14461,14466,14471],{"__ignoreMap":43},[48,14358,14359],{"class":50,"line":51},[48,14360,14361],{},"@synthesize pageIndex = _pageIndex;\n",[48,14363,14364],{"class":50,"line":57},[48,14365,14366],{},"- (id) initWithParent:(UIView *)parent {\n",[48,14368,14369],{"class":50,"line":63},[48,14370,14371],{}," self = [super initWithParent:parent];\n",[48,14373,14374],{"class":50,"line":69},[48,14375,14376],{}," if (self == nil) {\n",[48,14378,14379],{"class":50,"line":75},[48,14380,14313],{},[48,14382,14383],{"class":50,"line":81},[48,14384,14385],{}," }\n",[48,14387,14388],{"class":50,"line":124},[48,14389,14390],{}," self.userInteractionEnabled = YES;\n",[48,14392,14393],{"class":50,"line":129},[48,14394,14395],{}," self.size = self.window.size;\n",[48,14397,14398],{"class":50,"line":204},[48,14399,14400],{}," [[UIImageView viewWithParent:self] setImageWithName:@\"dailies\"];\n",[48,14402,14403],{"class":50,"line":210},[48,14404,14405],{}," self.pageIndex = 0;\n",[48,14407,14408],{"class":50,"line":216},[48,14409,14323],{},[48,14411,14412],{"class":50,"line":357},[48,14413,266],{},[48,14415,14416],{"class":50,"line":363},[48,14417,14418],{},"- (void)setPageIndex:(int)index {\n",[48,14420,14421],{"class":50,"line":369},[48,14422,14423],{}," if (index \u003C 0 || index >= [self.subviews count]) {\n",[48,14425,14426],{"class":50,"line":1978},[48,14427,14428],{}," return;\n",[48,14430,14431],{"class":50,"line":1991},[48,14432,14385],{},[48,14434,14435],{"class":50,"line":2010},[48,14436,14437],{}," _pageIndex = index;\n",[48,14439,14440],{"class":50,"line":2023},[48,14441,14442],{}," for (int i = 0; i \u003C [self.subviews count]; i++) {\n",[48,14444,14445],{"class":50,"line":2036},[48,14446,14447],{}," UIImageView *page = [self.subviews objectAtIndex:i];\n",[48,14449,14450],{"class":50,"line":2048},[48,14451,14452],{}," page.x = (i \u003C _pageIndex) ? -self.width : (i > _pageIndex) ? self.width : 0;\n",[48,14454,14455],{"class":50,"line":2060},[48,14456,14385],{},[48,14458,14459],{"class":50,"line":2073},[48,14460,266],{},[48,14462,14463],{"class":50,"line":2081},[48,14464,14465],{},"- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {\n",[48,14467,14468],{"class":50,"line":2092},[48,14469,14470],{}," self.pageIndex++;\n",[48,14472,14473],{"class":50,"line":2098},[48,14474,266],{},[18,14476,14477],{},"With those two classes and a couple of screenshots, it is fairly easy to create an App that looks and feels almost real.\nI created a short demo video, which shows how easy it is to get a good feeling if your App is going to work or not:",[18,14479,14480],{},"Now don’t forget those are only screenshots and the App might need to load stuff over the network or do some animation,\nhence it might not feel exactly the same. However, this process of prototyping an UI is powerful enough to give you an\nidea, whether the workflow or the UI in general is going to “work” or needs some tweaking.",[18,14482,14483,14484,392],{},"You can download the source code for the two classes, along with a sample project\nfrom ",[22,14485,14488],{"href":14486,"rel":14487},"http://github.com/dlinsin/district9/tree/master/UIPrototyping/",[26],"github",[394,14490,396],{},{"title":43,"searchDepth":57,"depth":57,"links":14492},[],[6671,406],"2010-06-29T06:46:00","Before flying off to WWDC last month, I watched a whole bunch of sessions\\nfrom 2009. Among others a session on “Prototyping iPhone User Interfaces” by Bret Victor. If you haven’t watched it and\\nyou’ve got access to the WWDC videos – stop right here and watch the video!","https://synyx.de/blog/ui-prototyping-iphone-apps/",{},"/blog/ui-prototyping-iphone-apps",{"title":14251,"description":14500},"Before flying off to WWDC last month, I watched a whole bunch of sessions\nfrom 2009. Among others a session on “Prototyping iPhone User Interfaces” by Bret Victor. If you haven’t watched it and\nyou’ve got access to the WWDC videos – stop right here and watch the video!","blog/ui-prototyping-iphone-apps",[11601,14503,11603,14246,14504],"design","prototyping","Before flying off to WWDC last month, I watched a whole bunch of sessions from 2009. Among others a session on “Prototyping iPhone User Interfaces” by Bret Victor. If you…","hEZiAfMXrZSp1Im2rsk88x4HQuKpzIhV5W2xo11faBA",{"id":14508,"title":14509,"author":14510,"body":14511,"category":15610,"date":15611,"description":15612,"extension":409,"link":15613,"meta":15614,"navigation":177,"path":15615,"seo":15616,"slug":14515,"stem":15617,"tags":15618,"teaser":15624,"__hash__":15625},"blog/blog/android-and-self-signed-ssl-certificates.md","Android and self-signed ssl certificates",[6686],{"type":11,"value":14512,"toc":15608},[14513,14516,14519,14522,14525,15059,15062,15377,15386,15389,15418,15560,15603,15606],[14,14514,14509],{"id":14515},"android-and-self-signed-ssl-certificates",[18,14517,14518],{},"Dealing with self-signed ssl certificates is a real pain, because it’s not that simple to add them in your app and let\nandroid accept them.",[18,14520,14521],{},"But fortunately, there’s a workaround that uses an own SSLSocketFactory and an own TrustManager. With this, only your\nadded site is beeing able to be called, so theres no security issue.",[18,14523,14524],{},"First you have to create the SSLFactory:",[38,14526,14528],{"className":40,"code":14527,"language":42,"meta":43,"style":43},"\n/*\n * Licensed to the Apache Software Foundation (ASF) under one\n * or more contributor license agreements. See the NOTICE file\n * distributed with this work for additional information\n * regarding copyright ownership. The ASF licenses this file\n * to you under the Apache License, Version 2.0 (the\n * \"License\"); you may not use this file except in compliance\n * with the License. You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing,\n * software distributed under the License is distributed on an\n * \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n * KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations\n * under the License.\n */\nimport java.io.IOException;\nimport java.net.InetAddress;\nimport java.net.InetSocketAddress;\nimport java.net.Socket;\nimport java.net.UnknownHostException;\nimport javax.net.ssl.SSLContext;\nimport javax.net.ssl.SSLSocket;\nimport javax.net.ssl.TrustManager;\nimport org.apache.http.conn.ConnectTimeoutException;\nimport org.apache.http.conn.scheme.LayeredSocketFactory;\nimport org.apache.http.conn.scheme.SocketFactory;\nimport org.apache.http.params.HttpConnectionParams;\nimport org.apache.http.params.HttpParams;\n/**\n * This socket factory will create ssl socket that accepts self signed certificate\n *\n * @author olamy\n * @version $Id: EasySSLSocketFactory.java 765355 2009-04-15 20:59:07Z evenisse $\n * @since 1.2.3\n */\npublic class EasySSLSocketFactory implements SocketFactory, LayeredSocketFactory {\n private SSLContext sslcontext = null;\n private static SSLContext createEasySSLContext() throws IOException {\n try {\n SSLContext context = SSLContext.getInstance(\"TLS\");\n context.init(null, new TrustManager[] { new EasyX509TrustManager(null) }, null);\n return context;\n } catch (Exception e) {\n throw new IOException(e.getMessage());\n }\n }\n private SSLContext getSSLContext() throws IOException {\n if (this.sslcontext == null) {\n this.sslcontext = createEasySSLContext();\n }\n return this.sslcontext;\n }\n /**\n * @see org.apache.http.conn.scheme.SocketFactory#connectSocket(java.net.Socket, java.lang.String, int,\n * java.net.InetAddress, int, org.apache.http.params.HttpParams)\n */\n public Socket connectSocket(Socket sock, String host, int port, InetAddress localAddress, int localPort,\n HttpParams params) throws IOException, UnknownHostException, ConnectTimeoutException {\n int connTimeout = HttpConnectionParams.getConnectionTimeout(params);\n int soTimeout = HttpConnectionParams.getSoTimeout(params);\n InetSocketAddress remoteAddress = new InetSocketAddress(host, port);\n SSLSocket sslsock = (SSLSocket) ((sock != null) ? sock : createSocket());\n if ((localAddress != null) || (localPort > 0)) {\n // we need to bind explicitly\n if (localPort \u003C 0) {\n localPort = 0; // indicates \"any\"\n }\n InetSocketAddress isa = new InetSocketAddress(localAddress, localPort);\n sslsock.bind(isa);\n }\n sslsock.connect(remoteAddress, connTimeout);\n sslsock.setSoTimeout(soTimeout);\n return sslsock;\n }\n /**\n * @see org.apache.http.conn.scheme.SocketFactory#createSocket()\n */\n public Socket createSocket() throws IOException {\n return getSSLContext().getSocketFactory().createSocket();\n }\n /**\n * @see org.apache.http.conn.scheme.SocketFactory#isSecure(java.net.Socket)\n */\n public boolean isSecure(Socket socket) throws IllegalArgumentException {\n return true;\n }\n /**\n * @see org.apache.http.conn.scheme.LayeredSocketFactory#createSocket(java.net.Socket, java.lang.String, int,\n * boolean)\n */\n public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException,\n UnknownHostException {\n return getSSLContext().getSocketFactory().createSocket(socket, host, port, autoClose);\n }\n // -------------------------------------------------------------------\n // javadoc in org.apache.http.conn.scheme.SocketFactory says :\n // Both Object.equals() and Object.hashCode() must be overridden\n // for the correct operation of some connection managers\n // -------------------------------------------------------------------\n public boolean equals(Object obj) {\n return ((obj != null) && obj.getClass().equals(EasySSLSocketFactory.class));\n }\n public int hashCode() {\n return EasySSLSocketFactory.class.hashCode();\n }\n}\n\n",[45,14529,14530,14534,14539,14544,14549,14554,14559,14564,14569,14574,14579,14584,14588,14593,14598,14603,14608,14613,14618,14623,14628,14633,14638,14643,14648,14653,14658,14663,14668,14673,14678,14683,14688,14693,14698,14702,14707,14712,14717,14721,14726,14731,14736,14740,14745,14750,14755,14760,14765,14769,14773,14778,14783,14788,14792,14797,14801,14806,14811,14816,14821,14826,14831,14836,14841,14846,14851,14856,14861,14866,14871,14875,14880,14885,14889,14894,14899,14904,14908,14912,14917,14921,14926,14931,14935,14939,14944,14948,14953,14958,14962,14966,14971,14976,14980,14985,14990,14995,14999,15004,15009,15014,15019,15023,15028,15033,15037,15043,15049,15054],{"__ignoreMap":43},[48,14531,14532],{"class":50,"line":51},[48,14533,178],{"emptyLinePlaceholder":177},[48,14535,14536],{"class":50,"line":57},[48,14537,14538],{},"/*\n",[48,14540,14541],{"class":50,"line":63},[48,14542,14543],{}," * Licensed to the Apache Software Foundation (ASF) under one\n",[48,14545,14546],{"class":50,"line":69},[48,14547,14548],{}," * or more contributor license agreements. See the NOTICE file\n",[48,14550,14551],{"class":50,"line":75},[48,14552,14553],{}," * distributed with this work for additional information\n",[48,14555,14556],{"class":50,"line":81},[48,14557,14558],{}," * regarding copyright ownership. The ASF licenses this file\n",[48,14560,14561],{"class":50,"line":124},[48,14562,14563],{}," * to you under the Apache License, Version 2.0 (the\n",[48,14565,14566],{"class":50,"line":129},[48,14567,14568],{}," * \"License\"); you may not use this file except in compliance\n",[48,14570,14571],{"class":50,"line":204},[48,14572,14573],{}," * with the License. You may obtain a copy of the License at\n",[48,14575,14576],{"class":50,"line":210},[48,14577,14578],{}," *\n",[48,14580,14581],{"class":50,"line":216},[48,14582,14583],{}," * http://www.apache.org/licenses/LICENSE-2.0\n",[48,14585,14586],{"class":50,"line":357},[48,14587,14578],{},[48,14589,14590],{"class":50,"line":363},[48,14591,14592],{}," * Unless required by applicable law or agreed to in writing,\n",[48,14594,14595],{"class":50,"line":369},[48,14596,14597],{}," * software distributed under the License is distributed on an\n",[48,14599,14600],{"class":50,"line":1978},[48,14601,14602],{}," * \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n",[48,14604,14605],{"class":50,"line":1991},[48,14606,14607],{}," * KIND, either express or implied. See the License for the\n",[48,14609,14610],{"class":50,"line":2010},[48,14611,14612],{}," * specific language governing permissions and limitations\n",[48,14614,14615],{"class":50,"line":2023},[48,14616,14617],{}," * under the License.\n",[48,14619,14620],{"class":50,"line":2036},[48,14621,14622],{}," */\n",[48,14624,14625],{"class":50,"line":2048},[48,14626,14627],{},"import java.io.IOException;\n",[48,14629,14630],{"class":50,"line":2060},[48,14631,14632],{},"import java.net.InetAddress;\n",[48,14634,14635],{"class":50,"line":2073},[48,14636,14637],{},"import java.net.InetSocketAddress;\n",[48,14639,14640],{"class":50,"line":2081},[48,14641,14642],{},"import java.net.Socket;\n",[48,14644,14645],{"class":50,"line":2092},[48,14646,14647],{},"import java.net.UnknownHostException;\n",[48,14649,14650],{"class":50,"line":2098},[48,14651,14652],{},"import javax.net.ssl.SSLContext;\n",[48,14654,14655],{"class":50,"line":2106},[48,14656,14657],{},"import javax.net.ssl.SSLSocket;\n",[48,14659,14660],{"class":50,"line":2112},[48,14661,14662],{},"import javax.net.ssl.TrustManager;\n",[48,14664,14665],{"class":50,"line":2117},[48,14666,14667],{},"import org.apache.http.conn.ConnectTimeoutException;\n",[48,14669,14670],{"class":50,"line":2129},[48,14671,14672],{},"import org.apache.http.conn.scheme.LayeredSocketFactory;\n",[48,14674,14675],{"class":50,"line":2150},[48,14676,14677],{},"import org.apache.http.conn.scheme.SocketFactory;\n",[48,14679,14680],{"class":50,"line":2161},[48,14681,14682],{},"import org.apache.http.params.HttpConnectionParams;\n",[48,14684,14685],{"class":50,"line":2173},[48,14686,14687],{},"import org.apache.http.params.HttpParams;\n",[48,14689,14690],{"class":50,"line":2184},[48,14691,14692],{},"/**\n",[48,14694,14695],{"class":50,"line":2195},[48,14696,14697],{}," * This socket factory will create ssl socket that accepts self signed certificate\n",[48,14699,14700],{"class":50,"line":2207},[48,14701,14578],{},[48,14703,14704],{"class":50,"line":2215},[48,14705,14706],{}," * @author olamy\n",[48,14708,14709],{"class":50,"line":2222},[48,14710,14711],{}," * @version $Id: EasySSLSocketFactory.java 765355 2009-04-15 20:59:07Z evenisse $\n",[48,14713,14714],{"class":50,"line":2227},[48,14715,14716],{}," * @since 1.2.3\n",[48,14718,14719],{"class":50,"line":2232},[48,14720,14622],{},[48,14722,14723],{"class":50,"line":2244},[48,14724,14725],{},"public class EasySSLSocketFactory implements SocketFactory, LayeredSocketFactory {\n",[48,14727,14728],{"class":50,"line":2260},[48,14729,14730],{}," private SSLContext sslcontext = null;\n",[48,14732,14733],{"class":50,"line":2271},[48,14734,14735],{}," private static SSLContext createEasySSLContext() throws IOException {\n",[48,14737,14738],{"class":50,"line":2282},[48,14739,9309],{},[48,14741,14742],{"class":50,"line":2294},[48,14743,14744],{}," SSLContext context = SSLContext.getInstance(\"TLS\");\n",[48,14746,14747],{"class":50,"line":2305},[48,14748,14749],{}," context.init(null, new TrustManager[] { new EasyX509TrustManager(null) }, null);\n",[48,14751,14752],{"class":50,"line":2316},[48,14753,14754],{}," return context;\n",[48,14756,14757],{"class":50,"line":2323},[48,14758,14759],{}," } catch (Exception e) {\n",[48,14761,14762],{"class":50,"line":2330},[48,14763,14764],{}," throw new IOException(e.getMessage());\n",[48,14766,14767],{"class":50,"line":2335},[48,14768,2864],{},[48,14770,14771],{"class":50,"line":2340},[48,14772,261],{},[48,14774,14775],{"class":50,"line":2352},[48,14776,14777],{}," private SSLContext getSSLContext() throws IOException {\n",[48,14779,14780],{"class":50,"line":2377},[48,14781,14782],{}," if (this.sslcontext == null) {\n",[48,14784,14785],{"class":50,"line":2389},[48,14786,14787],{}," this.sslcontext = createEasySSLContext();\n",[48,14789,14790],{"class":50,"line":2400},[48,14791,2864],{},[48,14793,14794],{"class":50,"line":2411},[48,14795,14796],{}," return this.sslcontext;\n",[48,14798,14799],{"class":50,"line":2422},[48,14800,261],{},[48,14802,14803],{"class":50,"line":2434},[48,14804,14805],{}," /**\n",[48,14807,14808],{"class":50,"line":2441},[48,14809,14810],{}," * @see org.apache.http.conn.scheme.SocketFactory#connectSocket(java.net.Socket, java.lang.String, int,\n",[48,14812,14813],{"class":50,"line":2454},[48,14814,14815],{}," * java.net.InetAddress, int, org.apache.http.params.HttpParams)\n",[48,14817,14818],{"class":50,"line":2467},[48,14819,14820],{}," */\n",[48,14822,14823],{"class":50,"line":2478},[48,14824,14825],{}," public Socket connectSocket(Socket sock, String host, int port, InetAddress localAddress, int localPort,\n",[48,14827,14828],{"class":50,"line":2483},[48,14829,14830],{}," HttpParams params) throws IOException, UnknownHostException, ConnectTimeoutException {\n",[48,14832,14833],{"class":50,"line":2490},[48,14834,14835],{}," int connTimeout = HttpConnectionParams.getConnectionTimeout(params);\n",[48,14837,14838],{"class":50,"line":2495},[48,14839,14840],{}," int soTimeout = HttpConnectionParams.getSoTimeout(params);\n",[48,14842,14843],{"class":50,"line":2500},[48,14844,14845],{}," InetSocketAddress remoteAddress = new InetSocketAddress(host, port);\n",[48,14847,14848],{"class":50,"line":2512},[48,14849,14850],{}," SSLSocket sslsock = (SSLSocket) ((sock != null) ? sock : createSocket());\n",[48,14852,14853],{"class":50,"line":2536},[48,14854,14855],{}," if ((localAddress != null) || (localPort > 0)) {\n",[48,14857,14858],{"class":50,"line":2547},[48,14859,14860],{}," // we need to bind explicitly\n",[48,14862,14863],{"class":50,"line":2558},[48,14864,14865],{}," if (localPort \u003C 0) {\n",[48,14867,14868],{"class":50,"line":2569},[48,14869,14870],{}," localPort = 0; // indicates \"any\"\n",[48,14872,14873],{"class":50,"line":2580},[48,14874,10922],{},[48,14876,14877],{"class":50,"line":2592},[48,14878,14879],{}," InetSocketAddress isa = new InetSocketAddress(localAddress, localPort);\n",[48,14881,14882],{"class":50,"line":2599},[48,14883,14884],{}," sslsock.bind(isa);\n",[48,14886,14887],{"class":50,"line":2610},[48,14888,2864],{},[48,14890,14891],{"class":50,"line":2621},[48,14892,14893],{}," sslsock.connect(remoteAddress, connTimeout);\n",[48,14895,14896],{"class":50,"line":2630},[48,14897,14898],{}," sslsock.setSoTimeout(soTimeout);\n",[48,14900,14901],{"class":50,"line":2635},[48,14902,14903],{}," return sslsock;\n",[48,14905,14906],{"class":50,"line":2642},[48,14907,261],{},[48,14909,14910],{"class":50,"line":2647},[48,14911,14805],{},[48,14913,14914],{"class":50,"line":2652},[48,14915,14916],{}," * @see org.apache.http.conn.scheme.SocketFactory#createSocket()\n",[48,14918,14919],{"class":50,"line":2664},[48,14920,14820],{},[48,14922,14923],{"class":50,"line":2681},[48,14924,14925],{}," public Socket createSocket() throws IOException {\n",[48,14927,14928],{"class":50,"line":2692},[48,14929,14930],{}," return getSSLContext().getSocketFactory().createSocket();\n",[48,14932,14933],{"class":50,"line":2703},[48,14934,261],{},[48,14936,14937],{"class":50,"line":2714},[48,14938,14805],{},[48,14940,14941],{"class":50,"line":2725},[48,14942,14943],{}," * @see org.apache.http.conn.scheme.SocketFactory#isSecure(java.net.Socket)\n",[48,14945,14946],{"class":50,"line":2736},[48,14947,14820],{},[48,14949,14950],{"class":50,"line":2743},[48,14951,14952],{}," public boolean isSecure(Socket socket) throws IllegalArgumentException {\n",[48,14954,14955],{"class":50,"line":2750},[48,14956,14957],{}," return true;\n",[48,14959,14960],{"class":50,"line":2755},[48,14961,261],{},[48,14963,14964],{"class":50,"line":2760},[48,14965,14805],{},[48,14967,14968],{"class":50,"line":2772},[48,14969,14970],{}," * @see org.apache.http.conn.scheme.LayeredSocketFactory#createSocket(java.net.Socket, java.lang.String, int,\n",[48,14972,14973],{"class":50,"line":2792},[48,14974,14975],{}," * boolean)\n",[48,14977,14978],{"class":50,"line":2803},[48,14979,14820],{},[48,14981,14982],{"class":50,"line":2814},[48,14983,14984],{}," public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException,\n",[48,14986,14987],{"class":50,"line":2825},[48,14988,14989],{}," UnknownHostException {\n",[48,14991,14992],{"class":50,"line":2836},[48,14993,14994],{}," return getSSLContext().getSocketFactory().createSocket(socket, host, port, autoClose);\n",[48,14996,14997],{"class":50,"line":2847},[48,14998,261],{},[48,15000,15001],{"class":50,"line":2854},[48,15002,15003],{}," // -------------------------------------------------------------------\n",[48,15005,15006],{"class":50,"line":2861},[48,15007,15008],{}," // javadoc in org.apache.http.conn.scheme.SocketFactory says :\n",[48,15010,15011],{"class":50,"line":2867},[48,15012,15013],{}," // Both Object.equals() and Object.hashCode() must be overridden\n",[48,15015,15016],{"class":50,"line":2873},[48,15017,15018],{}," // for the correct operation of some connection managers\n",[48,15020,15021],{"class":50,"line":2878},[48,15022,15003],{},[48,15024,15025],{"class":50,"line":2884},[48,15026,15027],{}," public boolean equals(Object obj) {\n",[48,15029,15030],{"class":50,"line":2892},[48,15031,15032],{}," return ((obj != null) && obj.getClass().equals(EasySSLSocketFactory.class));\n",[48,15034,15035],{"class":50,"line":2900},[48,15036,261],{},[48,15038,15040],{"class":50,"line":15039},107,[48,15041,15042],{}," public int hashCode() {\n",[48,15044,15046],{"class":50,"line":15045},108,[48,15047,15048],{}," return EasySSLSocketFactory.class.hashCode();\n",[48,15050,15052],{"class":50,"line":15051},109,[48,15053,261],{},[48,15055,15057],{"class":50,"line":15056},110,[48,15058,266],{},[18,15060,15061],{},"And the TrustManager:",[38,15063,15065],{"className":40,"code":15064,"language":42,"meta":43,"style":43},"\n/*\n * Licensed to the Apache Software Foundation (ASF) under one\n * or more contributor license agreements. See the NOTICE file\n * distributed with this work for additional information\n * regarding copyright ownership. The ASF licenses this file\n * to you under the Apache License, Version 2.0 (the\n * \"License\"); you may not use this file except in compliance\n * with the License. You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing,\n * software distributed under the License is distributed on an\n * \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n * KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations\n * under the License.\n */\nimport java.security.KeyStore;\nimport java.security.KeyStoreException;\nimport java.security.NoSuchAlgorithmException;\nimport java.security.cert.CertificateException;\nimport java.security.cert.X509Certificate;\nimport javax.net.ssl.TrustManager;\nimport javax.net.ssl.TrustManagerFactory;\nimport javax.net.ssl.X509TrustManager;\n/**\n * @author olamy\n * @version $Id: EasyX509TrustManager.java 765355 2009-04-15 20:59:07Z evenisse $\n * @since 1.2.3\n */\npublic class EasyX509TrustManager implements X509TrustManager {\n private X509TrustManager standardTrustManager = null;\n /**\n * Constructor for EasyX509TrustManager.\n */\n public EasyX509TrustManager(KeyStore keystore) throws NoSuchAlgorithmException, KeyStoreException {\n super();\n TrustManagerFactory factory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());\n factory.init(keystore);\n TrustManager[] trustmanagers = factory.getTrustManagers();\n if (trustmanagers.length == 0) {\n throw new NoSuchAlgorithmException(\"no trust manager found\");\n }\n this.standardTrustManager = (X509TrustManager) trustmanagers[0];\n }\n /**\n * @see javax.net.ssl.X509TrustManager#checkClientTrusted(X509Certificate[],String authType)\n */\n public void checkClientTrusted(X509Certificate[] certificates, String authType) throws CertificateException {\n standardTrustManager.checkClientTrusted(certificates, authType);\n }\n /**\n * @see javax.net.ssl.X509TrustManager#checkServerTrusted(X509Certificate[],String authType)\n */\n public void checkServerTrusted(X509Certificate[] certificates, String authType) throws CertificateException {\n if ((certificates != null) && (certificates.length == 1)) {\n certificates[0].checkValidity();\n } else {\n standardTrustManager.checkServerTrusted(certificates, authType);\n }\n }\n /**\n * @see javax.net.ssl.X509TrustManager#getAcceptedIssuers()\n */\n public X509Certificate[] getAcceptedIssuers() {\n return this.standardTrustManager.getAcceptedIssuers();\n }\n}\n\n",[45,15066,15067,15071,15075,15079,15083,15087,15091,15095,15099,15103,15107,15111,15115,15119,15123,15127,15131,15135,15139,15143,15148,15153,15158,15163,15168,15172,15177,15182,15186,15190,15195,15199,15203,15208,15213,15217,15222,15226,15231,15236,15241,15246,15251,15256,15261,15265,15270,15274,15278,15283,15287,15292,15297,15301,15305,15310,15314,15319,15324,15329,15333,15338,15342,15346,15350,15355,15359,15364,15369,15373],{"__ignoreMap":43},[48,15068,15069],{"class":50,"line":51},[48,15070,178],{"emptyLinePlaceholder":177},[48,15072,15073],{"class":50,"line":57},[48,15074,14538],{},[48,15076,15077],{"class":50,"line":63},[48,15078,14543],{},[48,15080,15081],{"class":50,"line":69},[48,15082,14548],{},[48,15084,15085],{"class":50,"line":75},[48,15086,14553],{},[48,15088,15089],{"class":50,"line":81},[48,15090,14558],{},[48,15092,15093],{"class":50,"line":124},[48,15094,14563],{},[48,15096,15097],{"class":50,"line":129},[48,15098,14568],{},[48,15100,15101],{"class":50,"line":204},[48,15102,14573],{},[48,15104,15105],{"class":50,"line":210},[48,15106,14578],{},[48,15108,15109],{"class":50,"line":216},[48,15110,14583],{},[48,15112,15113],{"class":50,"line":357},[48,15114,14578],{},[48,15116,15117],{"class":50,"line":363},[48,15118,14592],{},[48,15120,15121],{"class":50,"line":369},[48,15122,14597],{},[48,15124,15125],{"class":50,"line":1978},[48,15126,14602],{},[48,15128,15129],{"class":50,"line":1991},[48,15130,14607],{},[48,15132,15133],{"class":50,"line":2010},[48,15134,14612],{},[48,15136,15137],{"class":50,"line":2023},[48,15138,14617],{},[48,15140,15141],{"class":50,"line":2036},[48,15142,14622],{},[48,15144,15145],{"class":50,"line":2048},[48,15146,15147],{},"import java.security.KeyStore;\n",[48,15149,15150],{"class":50,"line":2060},[48,15151,15152],{},"import java.security.KeyStoreException;\n",[48,15154,15155],{"class":50,"line":2073},[48,15156,15157],{},"import java.security.NoSuchAlgorithmException;\n",[48,15159,15160],{"class":50,"line":2081},[48,15161,15162],{},"import java.security.cert.CertificateException;\n",[48,15164,15165],{"class":50,"line":2092},[48,15166,15167],{},"import java.security.cert.X509Certificate;\n",[48,15169,15170],{"class":50,"line":2098},[48,15171,14662],{},[48,15173,15174],{"class":50,"line":2106},[48,15175,15176],{},"import javax.net.ssl.TrustManagerFactory;\n",[48,15178,15179],{"class":50,"line":2112},[48,15180,15181],{},"import javax.net.ssl.X509TrustManager;\n",[48,15183,15184],{"class":50,"line":2117},[48,15185,14692],{},[48,15187,15188],{"class":50,"line":2129},[48,15189,14706],{},[48,15191,15192],{"class":50,"line":2150},[48,15193,15194],{}," * @version $Id: EasyX509TrustManager.java 765355 2009-04-15 20:59:07Z evenisse $\n",[48,15196,15197],{"class":50,"line":2161},[48,15198,14716],{},[48,15200,15201],{"class":50,"line":2173},[48,15202,14622],{},[48,15204,15205],{"class":50,"line":2184},[48,15206,15207],{},"public class EasyX509TrustManager implements X509TrustManager {\n",[48,15209,15210],{"class":50,"line":2195},[48,15211,15212],{}," private X509TrustManager standardTrustManager = null;\n",[48,15214,15215],{"class":50,"line":2207},[48,15216,14805],{},[48,15218,15219],{"class":50,"line":2215},[48,15220,15221],{}," * Constructor for EasyX509TrustManager.\n",[48,15223,15224],{"class":50,"line":2222},[48,15225,14820],{},[48,15227,15228],{"class":50,"line":2227},[48,15229,15230],{}," public EasyX509TrustManager(KeyStore keystore) throws NoSuchAlgorithmException, KeyStoreException {\n",[48,15232,15233],{"class":50,"line":2232},[48,15234,15235],{}," super();\n",[48,15237,15238],{"class":50,"line":2244},[48,15239,15240],{}," TrustManagerFactory factory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());\n",[48,15242,15243],{"class":50,"line":2260},[48,15244,15245],{}," factory.init(keystore);\n",[48,15247,15248],{"class":50,"line":2271},[48,15249,15250],{}," TrustManager[] trustmanagers = factory.getTrustManagers();\n",[48,15252,15253],{"class":50,"line":2282},[48,15254,15255],{}," if (trustmanagers.length == 0) {\n",[48,15257,15258],{"class":50,"line":2294},[48,15259,15260],{}," throw new NoSuchAlgorithmException(\"no trust manager found\");\n",[48,15262,15263],{"class":50,"line":2305},[48,15264,2864],{},[48,15266,15267],{"class":50,"line":2316},[48,15268,15269],{}," this.standardTrustManager = (X509TrustManager) trustmanagers[0];\n",[48,15271,15272],{"class":50,"line":2323},[48,15273,261],{},[48,15275,15276],{"class":50,"line":2330},[48,15277,14805],{},[48,15279,15280],{"class":50,"line":2335},[48,15281,15282],{}," * @see javax.net.ssl.X509TrustManager#checkClientTrusted(X509Certificate[],String authType)\n",[48,15284,15285],{"class":50,"line":2340},[48,15286,14820],{},[48,15288,15289],{"class":50,"line":2352},[48,15290,15291],{}," public void checkClientTrusted(X509Certificate[] certificates, String authType) throws CertificateException {\n",[48,15293,15294],{"class":50,"line":2377},[48,15295,15296],{}," standardTrustManager.checkClientTrusted(certificates, authType);\n",[48,15298,15299],{"class":50,"line":2389},[48,15300,261],{},[48,15302,15303],{"class":50,"line":2400},[48,15304,14805],{},[48,15306,15307],{"class":50,"line":2411},[48,15308,15309],{}," * @see javax.net.ssl.X509TrustManager#checkServerTrusted(X509Certificate[],String authType)\n",[48,15311,15312],{"class":50,"line":2422},[48,15313,14820],{},[48,15315,15316],{"class":50,"line":2434},[48,15317,15318],{}," public void checkServerTrusted(X509Certificate[] certificates, String authType) throws CertificateException {\n",[48,15320,15321],{"class":50,"line":2441},[48,15322,15323],{}," if ((certificates != null) && (certificates.length == 1)) {\n",[48,15325,15326],{"class":50,"line":2454},[48,15327,15328],{}," certificates[0].checkValidity();\n",[48,15330,15331],{"class":50,"line":2467},[48,15332,9348],{},[48,15334,15335],{"class":50,"line":2478},[48,15336,15337],{}," standardTrustManager.checkServerTrusted(certificates, authType);\n",[48,15339,15340],{"class":50,"line":2483},[48,15341,2864],{},[48,15343,15344],{"class":50,"line":2490},[48,15345,261],{},[48,15347,15348],{"class":50,"line":2495},[48,15349,14805],{},[48,15351,15352],{"class":50,"line":2500},[48,15353,15354],{}," * @see javax.net.ssl.X509TrustManager#getAcceptedIssuers()\n",[48,15356,15357],{"class":50,"line":2512},[48,15358,14820],{},[48,15360,15361],{"class":50,"line":2536},[48,15362,15363],{}," public X509Certificate[] getAcceptedIssuers() {\n",[48,15365,15366],{"class":50,"line":2547},[48,15367,15368],{}," return this.standardTrustManager.getAcceptedIssuers();\n",[48,15370,15371],{"class":50,"line":2558},[48,15372,261],{},[48,15374,15375],{"class":50,"line":2569},[48,15376,266],{},[18,15378,15379,15380,15385],{},"(Both classes are\nfrom ",[22,15381,15384],{"href":15382,"rel":15383},"https://web.archive.org/web/20111230175916/http://exchangeit.googlecode.com:80/svn-history/r23/trunk/src/com/byarger/exchangeit/",[26],"exchangeit","\nwith a small change on the EasySSLSocketFactory to work on android 2.2)",[18,15387,15388],{},"Now we have to do some other preparations and create a HttpClient that we can use to establish the connection:",[38,15390,15392],{"className":40,"code":15391,"language":42,"meta":43,"style":43},"\n//members\nprivate ClientConnectionManager clientConnectionManager;\nprivate HttpContext context;\nprivate HttpParams params;\n\n",[45,15393,15394,15398,15403,15408,15413],{"__ignoreMap":43},[48,15395,15396],{"class":50,"line":51},[48,15397,178],{"emptyLinePlaceholder":177},[48,15399,15400],{"class":50,"line":57},[48,15401,15402],{},"//members\n",[48,15404,15405],{"class":50,"line":63},[48,15406,15407],{},"private ClientConnectionManager clientConnectionManager;\n",[48,15409,15410],{"class":50,"line":69},[48,15411,15412],{},"private HttpContext context;\n",[48,15414,15415],{"class":50,"line":75},[48,15416,15417],{},"private HttpParams params;\n",[38,15419,15421],{"className":40,"code":15420,"language":42,"meta":43,"style":43},"\n//constructor\npublic WebService(){\n setup();\n}\n//prepare for the https connection\n//call this in the constructor of the class that does the connection if\n//it's used multiple times\nprivate void setup(){\nSchemeRegistry schemeRegistry = new SchemeRegistry();\n // http scheme\n schemeRegistry.register(new Scheme(\"http\", PlainSocketFactory.getSocketFactory(), 80));\n // https scheme\n schemeRegistry.register(new Scheme(\"https\", new EasySSLSocketFactory(), 443));\n params = new BasicHttpParams();\n params.setParameter(ConnManagerPNames.MAX_TOTAL_CONNECTIONS, 1);\n params.setParameter(ConnManagerPNames.MAX_CONNECTIONS_PER_ROUTE, new ConnPerRouteBean(1));\n params.setParameter(HttpProtocolParams.USE_EXPECT_CONTINUE, false);\n HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);\n HttpProtocolParams.setContentCharset(params, \"utf8\");\n CredentialsProvider credentialsProvider = new BasicCredentialsProvider();\n //set the user credentials for our site \"example.com\"\n credentialsProvider.setCredentials(new AuthScope(\"example.com\", AuthScope.ANY_PORT),\n new UsernamePasswordCredentials(\"UserNameHere\", \"UserPasswordHere\"));\n clientConnectionManager = new ThreadSafeClientConnManager(params, schemeRegistry);\n context = new BasicHttpContext();\n context.setAttribute(\"http.auth.credentials-provider\", credentialsProvider);\n}\n\n",[45,15422,15423,15427,15432,15437,15442,15446,15451,15456,15461,15466,15471,15476,15481,15486,15491,15496,15501,15506,15511,15516,15521,15526,15531,15536,15541,15546,15551,15556],{"__ignoreMap":43},[48,15424,15425],{"class":50,"line":51},[48,15426,178],{"emptyLinePlaceholder":177},[48,15428,15429],{"class":50,"line":57},[48,15430,15431],{},"//constructor\n",[48,15433,15434],{"class":50,"line":63},[48,15435,15436],{},"public WebService(){\n",[48,15438,15439],{"class":50,"line":69},[48,15440,15441],{}," setup();\n",[48,15443,15444],{"class":50,"line":75},[48,15445,266],{},[48,15447,15448],{"class":50,"line":81},[48,15449,15450],{},"//prepare for the https connection\n",[48,15452,15453],{"class":50,"line":124},[48,15454,15455],{},"//call this in the constructor of the class that does the connection if\n",[48,15457,15458],{"class":50,"line":129},[48,15459,15460],{},"//it's used multiple times\n",[48,15462,15463],{"class":50,"line":204},[48,15464,15465],{},"private void setup(){\n",[48,15467,15468],{"class":50,"line":210},[48,15469,15470],{},"SchemeRegistry schemeRegistry = new SchemeRegistry();\n",[48,15472,15473],{"class":50,"line":216},[48,15474,15475],{}," // http scheme\n",[48,15477,15478],{"class":50,"line":357},[48,15479,15480],{}," schemeRegistry.register(new Scheme(\"http\", PlainSocketFactory.getSocketFactory(), 80));\n",[48,15482,15483],{"class":50,"line":363},[48,15484,15485],{}," // https scheme\n",[48,15487,15488],{"class":50,"line":369},[48,15489,15490],{}," schemeRegistry.register(new Scheme(\"https\", new EasySSLSocketFactory(), 443));\n",[48,15492,15493],{"class":50,"line":1978},[48,15494,15495],{}," params = new BasicHttpParams();\n",[48,15497,15498],{"class":50,"line":1991},[48,15499,15500],{}," params.setParameter(ConnManagerPNames.MAX_TOTAL_CONNECTIONS, 1);\n",[48,15502,15503],{"class":50,"line":2010},[48,15504,15505],{}," params.setParameter(ConnManagerPNames.MAX_CONNECTIONS_PER_ROUTE, new ConnPerRouteBean(1));\n",[48,15507,15508],{"class":50,"line":2023},[48,15509,15510],{}," params.setParameter(HttpProtocolParams.USE_EXPECT_CONTINUE, false);\n",[48,15512,15513],{"class":50,"line":2036},[48,15514,15515],{}," HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);\n",[48,15517,15518],{"class":50,"line":2048},[48,15519,15520],{}," HttpProtocolParams.setContentCharset(params, \"utf8\");\n",[48,15522,15523],{"class":50,"line":2060},[48,15524,15525],{}," CredentialsProvider credentialsProvider = new BasicCredentialsProvider();\n",[48,15527,15528],{"class":50,"line":2073},[48,15529,15530],{}," //set the user credentials for our site \"example.com\"\n",[48,15532,15533],{"class":50,"line":2081},[48,15534,15535],{}," credentialsProvider.setCredentials(new AuthScope(\"example.com\", AuthScope.ANY_PORT),\n",[48,15537,15538],{"class":50,"line":2092},[48,15539,15540],{}," new UsernamePasswordCredentials(\"UserNameHere\", \"UserPasswordHere\"));\n",[48,15542,15543],{"class":50,"line":2098},[48,15544,15545],{}," clientConnectionManager = new ThreadSafeClientConnManager(params, schemeRegistry);\n",[48,15547,15548],{"class":50,"line":2106},[48,15549,15550],{}," context = new BasicHttpContext();\n",[48,15552,15553],{"class":50,"line":2112},[48,15554,15555],{}," context.setAttribute(\"http.auth.credentials-provider\", credentialsProvider);\n",[48,15557,15558],{"class":50,"line":2117},[48,15559,266],{},[38,15561,15563],{"className":40,"code":15562,"language":42,"meta":43,"style":43},"\npublic HttpResponse getResponseFromUrl(String url){\n//connection (client has to be created for every new connection)\nclient = new DefaultHttpClient(clientConnectionManager, params);\nHttpGet get = new HttpGet(url);\nHttpResponse response = client.execute(get, context);\nreturn response;\n}\n\n",[45,15564,15565,15569,15574,15579,15584,15589,15594,15599],{"__ignoreMap":43},[48,15566,15567],{"class":50,"line":51},[48,15568,178],{"emptyLinePlaceholder":177},[48,15570,15571],{"class":50,"line":57},[48,15572,15573],{},"public HttpResponse getResponseFromUrl(String url){\n",[48,15575,15576],{"class":50,"line":63},[48,15577,15578],{},"//connection (client has to be created for every new connection)\n",[48,15580,15581],{"class":50,"line":69},[48,15582,15583],{},"client = new DefaultHttpClient(clientConnectionManager, params);\n",[48,15585,15586],{"class":50,"line":75},[48,15587,15588],{},"HttpGet get = new HttpGet(url);\n",[48,15590,15591],{"class":50,"line":81},[48,15592,15593],{},"HttpResponse response = client.execute(get, context);\n",[48,15595,15596],{"class":50,"line":124},[48,15597,15598],{},"return response;\n",[48,15600,15601],{"class":50,"line":129},[48,15602,266],{},[18,15604,15605],{},"And that's it. I hope this will help some of you to solve their problems with self-signed certs!",[394,15607,396],{},{"title":43,"searchDepth":57,"depth":57,"links":15609},[],[6671,406],"2010-06-24T16:08:24","Dealing with self-signed ssl certificates is a real pain, because it’s not that simple to add them in your app and let\\nandroid accept them.","https://synyx.de/blog/android-and-self-signed-ssl-certificates/",{},"/blog/android-and-self-signed-ssl-certificates",{"title":14509,"description":14518},"blog/android-and-self-signed-ssl-certificates",[7380,15619,15620,15621,15622,15623],"cert","certificate","https","self-signed","ssl","Dealing with self-signed ssl certificates is a real pain, because it’s not that simple to add them in your app and let android accept them. But fortunately, there’s a workaround…","gDXsqYNJRaGcit1nh6xgZqXcXw3DL1tsKjV5BU5dPqA",{"id":15627,"title":15628,"author":15629,"body":15630,"category":16250,"date":16251,"description":16252,"extension":409,"link":16253,"meta":16254,"navigation":177,"path":16255,"seo":16256,"slug":15634,"stem":16258,"tags":16259,"teaser":16264,"__hash__":16265},"blog/blog/routing-driving-directions-on-android-part-2-draw-the-route.md","Routing / Driving directions on Android – Part 2: Draw the route",[6686],{"type":11,"value":15631,"toc":16245},[15632,15635,15644,15648,15651,15700,15703,15776,15779,15812,15816,15819,15822,15836,15839,15844,15851,15916,15919,16053,16057,16060,16063,16144,16147,16171,16174,16177,16180,16223,16226,16240,16243],[14,15633,15628],{"id":15634},"routing-driving-directions-on-android-part-2-draw-the-route",[18,15636,15637,15638,15643],{},"After you ",[22,15639,15642],{"href":15640,"rel":15641},"http://mobile.synyx.de/2010/06/routing-driving-directions-on-android-part-1-get-the-route/",[26],"got the route","\nfrom wherever, you probably want to draw it on a MapView. But how to do it? That’s what I will show you now.",[1822,15645,15647],{"id":15646},"create-a-suiting-overlay","Create a suiting Overlay",[18,15649,15650],{},"We basically need a Overlay that takes two Geopoints and maybe a color in which the lines should be drawn. So here We\nhave:",[38,15652,15654],{"className":40,"code":15653,"language":42,"meta":43,"style":43},"public class RouteOverlay extends Overlay {\n private GeoPoint gp1;\n private GeoPoint gp2;\n private int color;\npublic RouteOverlay(GeoPoint gp1, GeoPoint gp2, int color) {\n this.gp1 = gp1;\n this.gp2 = gp2;\n this.color = color;\n }\n",[45,15655,15656,15661,15666,15671,15676,15681,15686,15691,15696],{"__ignoreMap":43},[48,15657,15658],{"class":50,"line":51},[48,15659,15660],{},"public class RouteOverlay extends Overlay {\n",[48,15662,15663],{"class":50,"line":57},[48,15664,15665],{}," private GeoPoint gp1;\n",[48,15667,15668],{"class":50,"line":63},[48,15669,15670],{}," private GeoPoint gp2;\n",[48,15672,15673],{"class":50,"line":69},[48,15674,15675],{}," private int color;\n",[48,15677,15678],{"class":50,"line":75},[48,15679,15680],{},"public RouteOverlay(GeoPoint gp1, GeoPoint gp2, int color) {\n",[48,15682,15683],{"class":50,"line":81},[48,15684,15685],{}," this.gp1 = gp1;\n",[48,15687,15688],{"class":50,"line":124},[48,15689,15690],{}," this.gp2 = gp2;\n",[48,15692,15693],{"class":50,"line":129},[48,15694,15695],{}," this.color = color;\n",[48,15697,15698],{"class":50,"line":204},[48,15699,261],{},[18,15701,15702],{},"Now all that’s left now for our Overlay is to override the draw() method and draw the line as we need it:",[38,15704,15706],{"className":40,"code":15705,"language":42,"meta":43,"style":43},"@Override\npublic void draw(Canvas canvas, MapView mapView, boolean shadow) {\n Projection projection = mapView.getProjection();\n Paint paint = new Paint();\n Point point = new Point();\n projection.toPixels(gp1, point);\n paint.setColor(color);\n Point point2 = new Point();\n projection.toPixels(gp2, point2);\n paint.setStrokeWidth(5);\n paint.setAlpha(120);\n canvas.drawLine(point.x, point.y, point2.x, point2.y, paint);\n super.draw(canvas, mapView, shadow);\n}\n",[45,15707,15708,15712,15717,15722,15727,15732,15737,15742,15747,15752,15757,15762,15767,15772],{"__ignoreMap":43},[48,15709,15710],{"class":50,"line":51},[48,15711,8161],{},[48,15713,15714],{"class":50,"line":57},[48,15715,15716],{},"public void draw(Canvas canvas, MapView mapView, boolean shadow) {\n",[48,15718,15719],{"class":50,"line":63},[48,15720,15721],{}," Projection projection = mapView.getProjection();\n",[48,15723,15724],{"class":50,"line":69},[48,15725,15726],{}," Paint paint = new Paint();\n",[48,15728,15729],{"class":50,"line":75},[48,15730,15731],{}," Point point = new Point();\n",[48,15733,15734],{"class":50,"line":81},[48,15735,15736],{}," projection.toPixels(gp1, point);\n",[48,15738,15739],{"class":50,"line":124},[48,15740,15741],{}," paint.setColor(color);\n",[48,15743,15744],{"class":50,"line":129},[48,15745,15746],{}," Point point2 = new Point();\n",[48,15748,15749],{"class":50,"line":204},[48,15750,15751],{}," projection.toPixels(gp2, point2);\n",[48,15753,15754],{"class":50,"line":210},[48,15755,15756],{}," paint.setStrokeWidth(5);\n",[48,15758,15759],{"class":50,"line":216},[48,15760,15761],{}," paint.setAlpha(120);\n",[48,15763,15764],{"class":50,"line":357},[48,15765,15766],{}," canvas.drawLine(point.x, point.y, point2.x, point2.y, paint);\n",[48,15768,15769],{"class":50,"line":363},[48,15770,15771],{}," super.draw(canvas, mapView, shadow);\n",[48,15773,15774],{"class":50,"line":369},[48,15775,266],{},[18,15777,15778],{},"Back in the Activity, just iterate over the GeoPoints that you got from google maps and add each of them to the MapView:",[38,15780,15782],{"className":40,"code":15781,"language":42,"meta":43,"style":43},"private void drawPath(List geoPoints, int color) {\n List overlays = mapView.getOverlays();\n for (int i = 1; i \u003C geoPoints.size(); i++) {\n overlays.add(new RouteOverlay(geoPoints.get(i - 1), geoPoints.get(i), color));\n }\n}\n",[45,15783,15784,15789,15794,15799,15804,15808],{"__ignoreMap":43},[48,15785,15786],{"class":50,"line":51},[48,15787,15788],{},"private void drawPath(List geoPoints, int color) {\n",[48,15790,15791],{"class":50,"line":57},[48,15792,15793],{}," List overlays = mapView.getOverlays();\n",[48,15795,15796],{"class":50,"line":63},[48,15797,15798],{}," for (int i = 1; i \u003C geoPoints.size(); i++) {\n",[48,15800,15801],{"class":50,"line":69},[48,15802,15803],{}," overlays.add(new RouteOverlay(geoPoints.get(i - 1), geoPoints.get(i), color));\n",[48,15805,15806],{"class":50,"line":75},[48,15807,8655],{},[48,15809,15810],{"class":50,"line":81},[48,15811,266],{},[1822,15813,15815],{"id":15814},"get-location-updates-from-the-location-manager","Get location updates from the location manager",[18,15817,15818],{},"So now we have the geopoints and also the overlays, but we’ve only got the last known location of the user! The app\ndoesn’t even updates his location!",[18,15820,15821],{},"What we need to achieve this is a listener from the location manager. That’s quite simple and we also got the\nLocationManager ready in onCreate, so we just have to add this little line to it:",[38,15823,15825],{"className":40,"code":15824,"language":42,"meta":43,"style":43},"\nlocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 300000, 5000, this);\n\n",[45,15826,15827,15831],{"__ignoreMap":43},[48,15828,15829],{"class":50,"line":51},[48,15830,178],{"emptyLinePlaceholder":177},[48,15832,15833],{"class":50,"line":57},[48,15834,15835],{},"locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 300000, 5000, this);\n",[18,15837,15838],{},"The first number here is the timespan in milliseconds in which we want want to receive the updates, the second one is\nthe distance in meters that the user has to move before we get them. In our app we don’t have to update the route all\nthe time, so we go with 5 minutes and 5 kilometers.",[18,15840,15841],{},[974,15842,15843],{},"Be very careful with the values here, because the whole gps thing consumes a lot of energy! (And if the values are way\nto small it also blocks the whole app)",[18,15845,15846,15847,15850],{},"Also don’t forget to ",[974,15848,15849],{},"remove the listener"," if the MapView isn’t visible:",[38,15852,15854],{"className":40,"code":15853,"language":42,"meta":43,"style":43},"\n@Override\n protected void onPause() {\n //remove the listener\n locationManager.removeUpdates(this);\n super.onPause();\n }\n@Override\n protected void onResume() {\n //add the listener again\n locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 300000, 5000, this);\n super.onResume();\n }\n\n",[45,15855,15856,15860,15864,15869,15874,15879,15884,15888,15892,15897,15902,15907,15912],{"__ignoreMap":43},[48,15857,15858],{"class":50,"line":51},[48,15859,178],{"emptyLinePlaceholder":177},[48,15861,15862],{"class":50,"line":57},[48,15863,8161],{},[48,15865,15866],{"class":50,"line":63},[48,15867,15868],{}," protected void onPause() {\n",[48,15870,15871],{"class":50,"line":69},[48,15872,15873],{}," //remove the listener\n",[48,15875,15876],{"class":50,"line":75},[48,15877,15878],{}," locationManager.removeUpdates(this);\n",[48,15880,15881],{"class":50,"line":81},[48,15882,15883],{}," super.onPause();\n",[48,15885,15886],{"class":50,"line":124},[48,15887,261],{},[48,15889,15890],{"class":50,"line":129},[48,15891,8161],{},[48,15893,15894],{"class":50,"line":204},[48,15895,15896],{}," protected void onResume() {\n",[48,15898,15899],{"class":50,"line":210},[48,15900,15901],{}," //add the listener again\n",[48,15903,15904],{"class":50,"line":216},[48,15905,15906],{}," locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 300000, 5000, this);\n",[48,15908,15909],{"class":50,"line":357},[48,15910,15911],{}," super.onResume();\n",[48,15913,15914],{"class":50,"line":363},[48,15915,261],{},[18,15917,15918],{},"Now we have to let our MapActivity implement LocationListener and react to the updates:",[38,15920,15922],{"className":40,"code":15921,"language":42,"meta":43,"style":43},"\npublic class RouteActivity extends MapActivity implements LocationListener {\n@Override\npublic void onLocationChanged(Location location) {\ndrawUserPosition(location);\n}\nprivate void drawUserPosition(Location location) {\n GeoPoint currentLocation;\n currentLocation = new GeoPoint((int) ( location.getLatitude() * 1E6), (int) ( location\n getLongitude() * 1E6));\n OverlayItem currentLocationOverlay = new OverlayItem(currentLocation, getString(R.string.your_location),\n getString(R.string.current_location));\n mapOverlays.clear();\n if (locationOverlays.size() > 1) {\n // remove the old user position if there is one\n locationOverlays.removeOverlay(1);\n }\n //add new user position\n locationOverlays.addOverlay(currentLocationOverlay, this.getResources().getDrawable(R.drawable.someImage));\n mapOverlays.add(locationOverlays);\n //.\n //. calculate / set the mapcenter, zoom to span\n //. see in previous posts\n //.\n RouteThread rt = new RouteThread(currentLocation, synyxGeoPoint, routeHandler);\n rt.start();\n}\n",[45,15923,15924,15928,15933,15937,15942,15947,15951,15956,15961,15966,15971,15976,15981,15986,15991,15996,16001,16005,16010,16015,16020,16025,16030,16035,16039,16044,16049],{"__ignoreMap":43},[48,15925,15926],{"class":50,"line":51},[48,15927,178],{"emptyLinePlaceholder":177},[48,15929,15930],{"class":50,"line":57},[48,15931,15932],{},"public class RouteActivity extends MapActivity implements LocationListener {\n",[48,15934,15935],{"class":50,"line":63},[48,15936,8161],{},[48,15938,15939],{"class":50,"line":69},[48,15940,15941],{},"public void onLocationChanged(Location location) {\n",[48,15943,15944],{"class":50,"line":75},[48,15945,15946],{},"drawUserPosition(location);\n",[48,15948,15949],{"class":50,"line":81},[48,15950,266],{},[48,15952,15953],{"class":50,"line":124},[48,15954,15955],{},"private void drawUserPosition(Location location) {\n",[48,15957,15958],{"class":50,"line":129},[48,15959,15960],{}," GeoPoint currentLocation;\n",[48,15962,15963],{"class":50,"line":204},[48,15964,15965],{}," currentLocation = new GeoPoint((int) ( location.getLatitude() * 1E6), (int) ( location\n",[48,15967,15968],{"class":50,"line":210},[48,15969,15970],{}," getLongitude() * 1E6));\n",[48,15972,15973],{"class":50,"line":216},[48,15974,15975],{}," OverlayItem currentLocationOverlay = new OverlayItem(currentLocation, getString(R.string.your_location),\n",[48,15977,15978],{"class":50,"line":357},[48,15979,15980],{}," getString(R.string.current_location));\n",[48,15982,15983],{"class":50,"line":363},[48,15984,15985],{}," mapOverlays.clear();\n",[48,15987,15988],{"class":50,"line":369},[48,15989,15990],{}," if (locationOverlays.size() > 1) {\n",[48,15992,15993],{"class":50,"line":1978},[48,15994,15995],{}," // remove the old user position if there is one\n",[48,15997,15998],{"class":50,"line":1991},[48,15999,16000],{}," locationOverlays.removeOverlay(1);\n",[48,16002,16003],{"class":50,"line":2010},[48,16004,8655],{},[48,16006,16007],{"class":50,"line":2023},[48,16008,16009],{}," //add new user position\n",[48,16011,16012],{"class":50,"line":2036},[48,16013,16014],{}," locationOverlays.addOverlay(currentLocationOverlay, this.getResources().getDrawable(R.drawable.someImage));\n",[48,16016,16017],{"class":50,"line":2048},[48,16018,16019],{}," mapOverlays.add(locationOverlays);\n",[48,16021,16022],{"class":50,"line":2060},[48,16023,16024],{}," //.\n",[48,16026,16027],{"class":50,"line":2073},[48,16028,16029],{}," //. calculate / set the mapcenter, zoom to span\n",[48,16031,16032],{"class":50,"line":2081},[48,16033,16034],{}," //. see in previous posts\n",[48,16036,16037],{"class":50,"line":2092},[48,16038,16024],{},[48,16040,16041],{"class":50,"line":2098},[48,16042,16043],{}," RouteThread rt = new RouteThread(currentLocation, synyxGeoPoint, routeHandler);\n",[48,16045,16046],{"class":50,"line":2106},[48,16047,16048],{}," rt.start();\n",[48,16050,16051],{"class":50,"line":2112},[48,16052,266],{},[1822,16054,16056],{"id":16055},"make-it-threaded","Make it threaded",[18,16058,16059],{},"But we’re not quite finished yet, because you can’t just put the internet connection and parsing into the main thread!\nThis would block the ui for quite a long time if the users internet connection isn’t so fast.",[18,16061,16062],{},"So what we have to do is to create a handler as an inner class of our Activity that takes messages from the thread which\ngets us the geopoints:",[38,16064,16066],{"className":40,"code":16065,"language":42,"meta":43,"style":43},"\nprivate class RouteHandler extends Handler {\n public void handleMessage(Message msg) {\n boolean error = msg.getData().getBoolean(\"error\", false);\n if (!error) {\n // set the geopoints (we can't just add the overlays\n // to the map here, because it's on a different thread\n geoPoints = (List\u003CGeoPoint>) msg.obj;\n post(updateRoute);\n } else {\n // maybe you want to show an error message here to\n // notice the user that the route can not be displayed\n // because there's no connection to the internet\n }\n }\n }\n",[45,16067,16068,16072,16077,16082,16087,16092,16097,16102,16107,16112,16117,16122,16127,16132,16136,16140],{"__ignoreMap":43},[48,16069,16070],{"class":50,"line":51},[48,16071,178],{"emptyLinePlaceholder":177},[48,16073,16074],{"class":50,"line":57},[48,16075,16076],{},"private class RouteHandler extends Handler {\n",[48,16078,16079],{"class":50,"line":63},[48,16080,16081],{}," public void handleMessage(Message msg) {\n",[48,16083,16084],{"class":50,"line":69},[48,16085,16086],{}," boolean error = msg.getData().getBoolean(\"error\", false);\n",[48,16088,16089],{"class":50,"line":75},[48,16090,16091],{}," if (!error) {\n",[48,16093,16094],{"class":50,"line":81},[48,16095,16096],{}," // set the geopoints (we can't just add the overlays\n",[48,16098,16099],{"class":50,"line":124},[48,16100,16101],{}," // to the map here, because it's on a different thread\n",[48,16103,16104],{"class":50,"line":129},[48,16105,16106],{}," geoPoints = (List\u003CGeoPoint>) msg.obj;\n",[48,16108,16109],{"class":50,"line":204},[48,16110,16111],{}," post(updateRoute);\n",[48,16113,16114],{"class":50,"line":210},[48,16115,16116],{}," } else {\n",[48,16118,16119],{"class":50,"line":216},[48,16120,16121],{}," // maybe you want to show an error message here to\n",[48,16123,16124],{"class":50,"line":357},[48,16125,16126],{}," // notice the user that the route can not be displayed\n",[48,16128,16129],{"class":50,"line":363},[48,16130,16131],{}," // because there's no connection to the internet\n",[48,16133,16134],{"class":50,"line":369},[48,16135,10922],{},[48,16137,16138],{"class":50,"line":1978},[48,16139,2864],{},[48,16141,16142],{"class":50,"line":1991},[48,16143,261],{},[18,16145,16146],{},"Send the geopoints from the RouteThread:",[38,16148,16150],{"className":40,"code":16149,"language":42,"meta":43,"style":43},"\nMessage msg = new Message();\nmsg.obj = decodePoly(encoded);\nhandler.dispatchMessage(msg);\n\n",[45,16151,16152,16156,16161,16166],{"__ignoreMap":43},[48,16153,16154],{"class":50,"line":51},[48,16155,178],{"emptyLinePlaceholder":177},[48,16157,16158],{"class":50,"line":57},[48,16159,16160],{},"Message msg = new Message();\n",[48,16162,16163],{"class":50,"line":63},[48,16164,16165],{},"msg.obj = decodePoly(encoded);\n",[48,16167,16168],{"class":50,"line":69},[48,16169,16170],{},"handler.dispatchMessage(msg);\n",[18,16172,16173],{},"(If you have further data to send, use a Bundle object and add it to the Message.)",[18,16175,16176],{},"And now we need the main thread to update the overlays if there are new geopoints available. Again, we need the handler\nto accomplish that, because it can start runnables into the same thread the handler was created in.",[18,16178,16179],{},"First we create a runnable in the Activity:",[38,16181,16183],{"className":40,"code":16182,"language":42,"meta":43,"style":43},"\nfinal Runnable updateRoute = new Runnable() {\n public void run() {\n // draw the path and then invalidate the mapview so that it redraws itself\n drawPath(geoPoints, Color.GREEN);\n mapView.invalidate();\n }\n };\n\n",[45,16184,16185,16189,16194,16199,16204,16209,16214,16218],{"__ignoreMap":43},[48,16186,16187],{"class":50,"line":51},[48,16188,178],{"emptyLinePlaceholder":177},[48,16190,16191],{"class":50,"line":57},[48,16192,16193],{},"final Runnable updateRoute = new Runnable() {\n",[48,16195,16196],{"class":50,"line":63},[48,16197,16198],{}," public void run() {\n",[48,16200,16201],{"class":50,"line":69},[48,16202,16203],{}," // draw the path and then invalidate the mapview so that it redraws itself\n",[48,16205,16206],{"class":50,"line":75},[48,16207,16208],{}," drawPath(geoPoints, Color.GREEN);\n",[48,16210,16211],{"class":50,"line":81},[48,16212,16213],{}," mapView.invalidate();\n",[48,16215,16216],{"class":50,"line":124},[48,16217,2864],{},[48,16219,16220],{"class":50,"line":129},[48,16221,16222],{}," };\n",[18,16224,16225],{},"And all that is left to do is to call it from inside the handler:",[38,16227,16229],{"className":40,"code":16228,"language":42,"meta":43,"style":43},"\npost(updateRoute);\n\n",[45,16230,16231,16235],{"__ignoreMap":43},[48,16232,16233],{"class":50,"line":51},[48,16234,178],{"emptyLinePlaceholder":177},[48,16236,16237],{"class":50,"line":57},[48,16238,16239],{},"post(updateRoute);\n",[18,16241,16242],{},"Anyway, this is how we solved the routing in our app. If you have a question, or have suggestions how we could make it\nbetter, please leave us a comment!",[394,16244,396],{},{"title":43,"searchDepth":57,"depth":57,"links":16246},[16247,16248,16249],{"id":15646,"depth":57,"text":15647},{"id":15814,"depth":57,"text":15815},{"id":16055,"depth":57,"text":16056},[6671,406],"2010-06-16T14:14:35","After you got the route\\nfrom wherever, you probably want to draw it on a MapView. But how to do it? That’s what I will show you now.","https://synyx.de/blog/routing-driving-directions-on-android-part-2-draw-the-route/",{},"/blog/routing-driving-directions-on-android-part-2-draw-the-route",{"title":15628,"description":16257},"After you got the route\nfrom wherever, you probably want to draw it on a MapView. But how to do it? That’s what I will show you now.","blog/routing-driving-directions-on-android-part-2-draw-the-route",[7380,16260,16261,16262,16263],"driving-directions","map","overlays","routing","After you got the route from wherever, you probably want to draw it on a MapView. But how to do it? That’s what I will show you now. Create a…","ef_SMhSPJEztFJzu7jCXik6lZ16Do7MuCSWGxxWmEuc",{"id":16267,"title":16268,"author":16269,"body":16270,"category":16746,"date":16747,"description":16748,"extension":409,"link":16749,"meta":16750,"navigation":177,"path":16751,"seo":16752,"slug":16274,"stem":16754,"tags":16755,"teaser":16757,"__hash__":16758},"blog/blog/routing-driving-directions-on-android-part-1-get-the-route.md","Routing / Driving directions on Android – Part 1: Get the route",[6686],{"type":11,"value":16271,"toc":16737},[16272,16275,16289,16292,16296,16299,16302,16311,16314,16349,16352,16356,16360,16363,16369,16377,16380,16457,16460,16497,16500,16503,16512,16516,16519,16522,16559,16568,16712,16716,16719,16727,16731,16734],[14,16273,16268],{"id":16274},"routing-driving-directions-on-android-part-1-get-the-route",[18,16276,16277,16278,4439,16283,16288],{},"Complementary to Sebastian’s posts\nabout ",[22,16279,16282],{"href":16280,"rel":16281},"http://mobile.synyx.de/2010/04/google-maps-on-android/",[26],"how to navigate with the MapView",[22,16284,16287],{"href":16285,"rel":16286},"http://mobile.synyx.de/2010/05/google-maps-on-android-part-2-overlays/",[26],"how to add customized overlays to it",", I\nwant to show you, how you display the route between two points.",[18,16290,16291],{},"Well, the whole thing wouldn’t be a pain now, if google hadn’t removed the DrivingDirection class since API 1.0, with\nwhich you could solve this problem in no time and with just a few lines of code. But because they did remove it, we have\nto go through a little bit more trouble.",[1822,16293,16295],{"id":16294},"getting-started","Getting started",[18,16297,16298],{},"First you have to realize, if you only need the coordinates of the route, or also driving directions like “go left on\nthis street, drive right on that street…”.",[18,16300,16301],{},"The first step in both cases is to get the two locations between which the routing should happen. In our case we wanted\nto show the route from the current location of the user to our office, so one of the points is fixed:",[38,16303,16305],{"className":40,"code":16304,"language":42,"meta":43,"style":43},"synyxGeoPoint = new GeoPoint(49002175, 8394160);\n",[45,16306,16307],{"__ignoreMap":43},[48,16308,16309],{"class":50,"line":51},[48,16310,16304],{},[18,16312,16313],{},"And the user location is also quite easy to get:",[38,16315,16317],{"className":40,"code":16316,"language":42,"meta":43,"style":43},"locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);\nCriteria criteria = new Criteria();\ncriteria.setAccuracy(Criteria.ACCURACY_FINE);\ncriteria.setAltitudeRequired(false);\nLocation lastKnownLocation =\nlocationManager.getLastKnownLocation(locationManager.getBestProvider(criteria, true));\n",[45,16318,16319,16324,16329,16334,16339,16344],{"__ignoreMap":43},[48,16320,16321],{"class":50,"line":51},[48,16322,16323],{},"locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);\n",[48,16325,16326],{"class":50,"line":57},[48,16327,16328],{},"Criteria criteria = new Criteria();\n",[48,16330,16331],{"class":50,"line":63},[48,16332,16333],{},"criteria.setAccuracy(Criteria.ACCURACY_FINE);\n",[48,16335,16336],{"class":50,"line":69},[48,16337,16338],{},"criteria.setAltitudeRequired(false);\n",[48,16340,16341],{"class":50,"line":75},[48,16342,16343],{},"Location lastKnownLocation =\n",[48,16345,16346],{"class":50,"line":81},[48,16347,16348],{},"locationManager.getLastKnownLocation(locationManager.getBestProvider(criteria, true));\n",[18,16350,16351],{},"With this we get the last known, accurate location of the user. So all there is left now, is to get the route.",[1822,16353,16355],{"id":16354},"getting-the-geopoints-from-google-maps","Getting the geopoints from google maps",[30,16357,16359],{"id":16358},"kml-with-driving-directions","Kml (with driving directions)",[18,16361,16362],{},"If you need the driving directions, you can build yourself a url like this one to get a kml file with all the\ninformation:",[18,16364,16365],{},[22,16366,16367],{"href":16367,"rel":16368},"http://maps.google.com/maps?f=d&hl=en&saddr=9.18333,48.7667&daddr=8.394160,49.002175&ie=UTF8&0&om=0&z=20&output=kml",[26],[18,16370,16371,16372,6758],{},"(For the list of parameters of google maps, see\nhere: ",[22,16373,16376],{"href":16374,"rel":16375},"https://web.archive.org/web/20080901081831/http://mapki.com/wiki/Google_Map_Parameters",[26],"mapki.com",[18,16378,16379],{},"Here’s how we did it:",[38,16381,16383],{"className":40,"code":16382,"language":42,"meta":43,"style":43},"\npublic String getUrl(GeoPoint src, GeoPoint dest){\nStringBuilder urlString = new StringBuilder();\nurlString.append(\"http://maps.google.com/maps?f=d&hl=en\");\nurlString.append(\"&saddr=\");\nurlString.append(Double.toString((double) src.getLatitudeE6() / 1.0E6));\nurlString.append(\",\");\nurlString.append(Double.toString((double) src.getLongitudeE6() / 1.0E6));\nurlString.append(\"&daddr=\");// to\nurlString.append(Double.toString((double) dest.getLatitudeE6() / 1.0E6));\nurlString.append(\",\");\nurlString.append(Double.toString((double) dest.getLongitudeE6() / 1.0E6));\nurlString.append(\"&ie=UTF8&0&om=0&output=kml\");\nreturn urlString;\n}\n",[45,16384,16385,16389,16394,16399,16404,16409,16414,16419,16424,16429,16434,16438,16443,16448,16453],{"__ignoreMap":43},[48,16386,16387],{"class":50,"line":51},[48,16388,178],{"emptyLinePlaceholder":177},[48,16390,16391],{"class":50,"line":57},[48,16392,16393],{},"public String getUrl(GeoPoint src, GeoPoint dest){\n",[48,16395,16396],{"class":50,"line":63},[48,16397,16398],{},"StringBuilder urlString = new StringBuilder();\n",[48,16400,16401],{"class":50,"line":69},[48,16402,16403],{},"urlString.append(\"http://maps.google.com/maps?f=d&hl=en\");\n",[48,16405,16406],{"class":50,"line":75},[48,16407,16408],{},"urlString.append(\"&saddr=\");\n",[48,16410,16411],{"class":50,"line":81},[48,16412,16413],{},"urlString.append(Double.toString((double) src.getLatitudeE6() / 1.0E6));\n",[48,16415,16416],{"class":50,"line":124},[48,16417,16418],{},"urlString.append(\",\");\n",[48,16420,16421],{"class":50,"line":129},[48,16422,16423],{},"urlString.append(Double.toString((double) src.getLongitudeE6() / 1.0E6));\n",[48,16425,16426],{"class":50,"line":204},[48,16427,16428],{},"urlString.append(\"&daddr=\");// to\n",[48,16430,16431],{"class":50,"line":210},[48,16432,16433],{},"urlString.append(Double.toString((double) dest.getLatitudeE6() / 1.0E6));\n",[48,16435,16436],{"class":50,"line":216},[48,16437,16418],{},[48,16439,16440],{"class":50,"line":357},[48,16441,16442],{},"urlString.append(Double.toString((double) dest.getLongitudeE6() / 1.0E6));\n",[48,16444,16445],{"class":50,"line":363},[48,16446,16447],{},"urlString.append(\"&ie=UTF8&0&om=0&output=kml\");\n",[48,16449,16450],{"class":50,"line":369},[48,16451,16452],{},"return urlString;\n",[48,16454,16455],{"class":50,"line":1978},[48,16456,266],{},[18,16458,16459],{},"The file you get from this url looks like this:",[38,16461,16463],{"className":3414,"code":16462,"language":3416,"meta":43,"style":43},"\nHohenheimer Str./B27 to Karlstraße/L561\n....\nHead southeast on Hohenheimer Str./B27 toward Bopserwaldstraße Continue to follow B27\n....\n9.183560,48.766820,0.000000 9.183690,48.766670,0.000000 9.183640,48.766480,0.000000 9.183470,48.766380,0.000000\n....\n",[45,16464,16465,16469,16474,16479,16484,16488,16493],{"__ignoreMap":43},[48,16466,16467],{"class":50,"line":51},[48,16468,178],{"emptyLinePlaceholder":177},[48,16470,16471],{"class":50,"line":57},[48,16472,16473],{},"Hohenheimer Str./B27 to Karlstraße/L561\n",[48,16475,16476],{"class":50,"line":63},[48,16477,16478],{},"....\n",[48,16480,16481],{"class":50,"line":69},[48,16482,16483],{},"Head southeast on Hohenheimer Str./B27 toward Bopserwaldstraße Continue to follow B27\n",[48,16485,16486],{"class":50,"line":75},[48,16487,16478],{},[48,16489,16490],{"class":50,"line":81},[48,16491,16492],{},"9.183560,48.766820,0.000000 9.183690,48.766670,0.000000 9.183640,48.766480,0.000000 9.183470,48.766380,0.000000\n",[48,16494,16495],{"class":50,"line":124},[48,16496,16478],{},[18,16498,16499],{},"(the first number of each pair is the longitude, the second the latitude)",[18,16501,16502],{},"To convert them to GeoPoints use:",[38,16504,16506],{"className":40,"code":16505,"language":42,"meta":43,"style":43},"GeoPoint geoPoint = new GeoPoint((int)(Double.parseDouble(latitude[0])*1E6),(int)(Double.parseDouble(longitude[0])*1E6));\n",[45,16507,16508],{"__ignoreMap":43},[48,16509,16510],{"class":50,"line":51},[48,16511,16505],{},[30,16513,16515],{"id":16514},"json-without-driving-directions","JSON (without driving directions)",[18,16517,16518],{},"If you don’t need the driving directions, you can save a few kilobytes by changing the output parameter to\noutput=dragdir (else it’s exactly the same url as above) , which delivers you a json string with encrypted geopoints.",[18,16520,16521],{},"again an example of what the server returns:",[38,16523,16525],{"className":11361,"code":16524,"language":11363,"meta":43,"style":43},"{tooltipHtml:\" (572x26#160;km / 5 hours 14 mins)\",polylines:[{id:\"route0\",points:\"se}bIgcwt@BSzA_D??Xh@dC|G??hDlIpBzFrAvC`@`BZjCV|@nApBtDvEx@rA| .....\n",[45,16526,16527],{"__ignoreMap":43},[48,16528,16529,16532,16535,16537,16540,16542,16545,16548,16551,16554,16557],{"class":50,"line":51},[48,16530,16531],{"class":482},"{",[48,16533,16534],{"class":467},"tooltipHtml",[48,16536,3723],{"class":482},[48,16538,16539],{"class":475},"\" (572x26#160;km / 5 hours 14 mins)\"",[48,16541,489],{"class":482},[48,16543,16544],{"class":467},"polylines",[48,16546,16547],{"class":482},":[{id:",[48,16549,16550],{"class":475},"\"route0\"",[48,16552,16553],{"class":482},",points:",[48,16555,16556],{"class":475},"\"se}bIgcwt@BSzA_D??Xh@dC|G??hDlIpBzFrAvC`@`BZjCV|@nApBtDvEx@rA| ....",[48,16558,5789],{"class":6852},[18,16560,16561,16562,16567],{},"So as you can see, you can’t just parse the string and get the geopoints out of it. You first have to decode them.\nHere’s a method that solves this for you (algorithm\nfrom ",[22,16563,16566],{"href":16564,"rel":16565},"http://facstaff.unca.edu/mcmcclur/googlemaps/encodepolyline/",[26],"http://facstaff.unca.edu/",") :",[38,16569,16571],{"className":40,"code":16570,"language":42,"meta":43,"style":43},"// get only the encoded geopoints\nencoded = encoded.split(\"points:\"\")[1].split(\"\",\")[0];\n// replace two backslashes by one (some error from the transmission)\nencoded = encoded.replace(\"\\\\\", \"\\\");\n//decoding\nList poly = new ArrayList();\n int index = 0, len = encoded.length();\n int lat = 0, lng = 0;\n while (index \u003C len) {\n int b, shift = 0, result = 0;\n do {\n b = encoded.charAt(index++) - 63;\n result |= (b & 0x1f) \u003C\u003C shift;\n shift += 5;\n } while (b >= 0x20);\n int dlat = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));\n lat += dlat;\n shift = 0;\n result = 0;\n do {\n b = encoded.charAt(index++) - 63;\n result |= (b & 0x1f) \u003C\u003C shift;\n shift += 5;\n } while (b >= 0x20);\n int dlng = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));\n lng += dlng;\n GeoPoint p = new GeoPoint((int) (((double) lat / 1E5) * 1E6), (int) (((double) lng / 1E5) * 1E6));\n poly.add(p);\n }\n",[45,16572,16573,16578,16583,16588,16593,16598,16603,16608,16613,16618,16623,16628,16633,16638,16643,16648,16653,16658,16663,16668,16672,16676,16680,16684,16688,16693,16698,16703,16708],{"__ignoreMap":43},[48,16574,16575],{"class":50,"line":51},[48,16576,16577],{},"// get only the encoded geopoints\n",[48,16579,16580],{"class":50,"line":57},[48,16581,16582],{},"encoded = encoded.split(\"points:\"\")[1].split(\"\",\")[0];\n",[48,16584,16585],{"class":50,"line":63},[48,16586,16587],{},"// replace two backslashes by one (some error from the transmission)\n",[48,16589,16590],{"class":50,"line":69},[48,16591,16592],{},"encoded = encoded.replace(\"\\\\\", \"\\\");\n",[48,16594,16595],{"class":50,"line":75},[48,16596,16597],{},"//decoding\n",[48,16599,16600],{"class":50,"line":81},[48,16601,16602],{},"List poly = new ArrayList();\n",[48,16604,16605],{"class":50,"line":124},[48,16606,16607],{}," int index = 0, len = encoded.length();\n",[48,16609,16610],{"class":50,"line":129},[48,16611,16612],{}," int lat = 0, lng = 0;\n",[48,16614,16615],{"class":50,"line":204},[48,16616,16617],{}," while (index \u003C len) {\n",[48,16619,16620],{"class":50,"line":210},[48,16621,16622],{}," int b, shift = 0, result = 0;\n",[48,16624,16625],{"class":50,"line":216},[48,16626,16627],{}," do {\n",[48,16629,16630],{"class":50,"line":357},[48,16631,16632],{}," b = encoded.charAt(index++) - 63;\n",[48,16634,16635],{"class":50,"line":363},[48,16636,16637],{}," result |= (b & 0x1f) \u003C\u003C shift;\n",[48,16639,16640],{"class":50,"line":369},[48,16641,16642],{}," shift += 5;\n",[48,16644,16645],{"class":50,"line":1978},[48,16646,16647],{}," } while (b >= 0x20);\n",[48,16649,16650],{"class":50,"line":1991},[48,16651,16652],{}," int dlat = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));\n",[48,16654,16655],{"class":50,"line":2010},[48,16656,16657],{}," lat += dlat;\n",[48,16659,16660],{"class":50,"line":2023},[48,16661,16662],{}," shift = 0;\n",[48,16664,16665],{"class":50,"line":2036},[48,16666,16667],{}," result = 0;\n",[48,16669,16670],{"class":50,"line":2048},[48,16671,16627],{},[48,16673,16674],{"class":50,"line":2060},[48,16675,16632],{},[48,16677,16678],{"class":50,"line":2073},[48,16679,16637],{},[48,16681,16682],{"class":50,"line":2081},[48,16683,16642],{},[48,16685,16686],{"class":50,"line":2092},[48,16687,16647],{},[48,16689,16690],{"class":50,"line":2098},[48,16691,16692],{}," int dlng = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));\n",[48,16694,16695],{"class":50,"line":2106},[48,16696,16697],{}," lng += dlng;\n",[48,16699,16700],{"class":50,"line":2112},[48,16701,16702],{}," GeoPoint p = new GeoPoint((int) (((double) lat / 1E5) * 1E6), (int) (((double) lng / 1E5) * 1E6));\n",[48,16704,16705],{"class":50,"line":2117},[48,16706,16707],{}," poly.add(p);\n",[48,16709,16710],{"class":50,"line":2129},[48,16711,2864],{},[1822,16713,16715],{"id":16714},"getting-the-geopoints-from-open-streetmap","Getting the geopoints from open streetmap",[18,16717,16718],{},"You can also get the geopoints from open streetmap. It’s quite the same procedure, so i don’t write it all again.",[18,16720,16721,16722],{},"Here you can see for yourself: ",[22,16723,16726],{"href":16724,"rel":16725},"http://wiki.openstreetmap.org/wiki/YOURS#Routing_API",[26],"YOURS Routing_API",[1822,16728,16730],{"id":16729},"whats-in-the-next-post","What’s in the next post",[18,16732,16733],{},"That’s it for today, in the next post i will show you how to draw the route on your MapView properly.",[394,16735,16736],{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .s7hpK, html code.shiki .s7hpK{--shiki-default:#B31D28;--shiki-default-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic}",{"title":43,"searchDepth":57,"depth":57,"links":16738},[16739,16740,16744,16745],{"id":16294,"depth":57,"text":16295},{"id":16354,"depth":57,"text":16355,"children":16741},[16742,16743],{"id":16358,"depth":63,"text":16359},{"id":16514,"depth":63,"text":16515},{"id":16714,"depth":57,"text":16715},{"id":16729,"depth":57,"text":16730},[6671,406],"2010-06-14T14:00:19","Complementary to Sebastian’s posts\\nabout how to navigate with the MapView\\nand how to add customized overlays to it, I\\nwant to show you, how you display the route between two points.","https://synyx.de/blog/routing-driving-directions-on-android-part-1-get-the-route/",{},"/blog/routing-driving-directions-on-android-part-1-get-the-route",{"title":16268,"description":16753},"Complementary to Sebastian’s posts\nabout how to navigate with the MapView\nand how to add customized overlays to it, I\nwant to show you, how you display the route between two points.","blog/routing-driving-directions-on-android-part-1-get-the-route",[7380,16756,16261,16263],"driving-direction","Complementary to Sebastian’s posts about how to navigate with the MapView and how to add customized overlays to it, I want to show you, how you display the route between…","23Pc9hKpFDatuwCoBwCqHTTtAhn5eTixkOfHWMuYPBM",{"id":16760,"title":16761,"author":16762,"body":16763,"category":16899,"date":16900,"description":16770,"extension":409,"link":16901,"meta":16902,"navigation":177,"path":16903,"seo":16904,"slug":16767,"stem":16905,"tags":16906,"teaser":16909,"__hash__":16910},"blog/blog/google-maps-on-maemo-5-part-1.md","Google Maps on Maemo 5 Part 1",[14032],{"type":11,"value":16764,"toc":16896},[16765,16768,16771,16775,16783,16786,16789,16792,16795,16798,16801,16804,16807,16810,16813,16816,16818,16821,16824,16826,16829,16832,16835,16838,16840,16842,16845,16848,16851,16853,16855,16858,16861,16863,16866,16869,16871,16874,16879,16882,16887,16890,16893],[14,16766,16761],{"id":16767},"google-maps-on-maemo-5-part-1",[18,16769,16770],{},"In this post i will show you how to realize a Maemo 5 Qt 4.6 application with google maps integration.",[1822,16772,16774],{"id":16773},"the-map","The map:",[18,16776,16777,16778,16782],{},"There is a good short tutorial with included source code: ",[22,16779,16780],{"href":16780,"rel":16781},"http://efforts.embedded.ufcg.edu.br/qt/?p=80",[26],". Because there\nis no native library for the N900 you have to go another way to get the map in your application. The tutorial describes\nhowto load and render the map by the webkit library. I used parts of the html file of that project for my little app.",[18,16784,16785],{},"The idea is quite simple. Webkit will render a webpage insider your app. That webpage consists of javascript methods\nwhich use the Google Maps-API. The javascript methods can be triggered from the app. The map class acts as proxy for\nthe comunication between your app and the website. Quite simple hm?",[18,16787,16788],{},"The benefit of that method is: you may use the complete power of Google Maps-API. But if your internet connection is\nslow, you will have to wait until the map is loaded and that may need some time and a white page just looks ugly. Native\nimplementations often have some startup animations that have an effect of compensation.",[18,16790,16791],{},"Sourcecode of index.html with javascript methods for google’s api:",[18,16793,16794],{},"`function initialize(){",[18,16796,16797],{},"map = new GMap2(document.getElementById(\"map\"));",[18,16799,16800],{},"map.setCenter( new GLatLng(49.002397,8.394251),10 );",[18,16802,16803],{},"var point = new GLatLng(49.002397,8.394251);",[18,16805,16806],{},"map.addOverlay(new GMarker(point));",[18,16808,16809],{},"openSynyx();",[18,16811,16812],{},"}`",[18,16814,16815],{},"`function openSynyx()",[18,16817,16531],{},[18,16819,16820],{},"map.setCenter( new GLatLng(49.002397,8.394251),15 );",[18,16822,16823],{},"map.openInfoWindow(map.getCenter(),document.createTextNode(\"Synyx GmbH & Co. KG\"));",[18,16825,16812],{},[18,16827,16828],{},"`function route(from){",[18,16830,16831],{},"map.clearOverlays();",[18,16833,16834],{},"directions = new GDirections(map);",[18,16836,16837],{},"directions.load(\"from: \"+from+\" to: Karlsruhe, Karlstrasse 68\");",[18,16839,16812],{},[18,16841,5786],{},[9019,16843],{"id":16261,"style":16844},"width: 450px; height: 400px",[18,16846,16847],{},"The map.cpp acts is a Children of QWebView and acts as poxy to the javascript methods:",[18,16849,16850],{},"`Map::Map(QWidget *parent) : QWebView(parent)",[18,16852,16531],{},[18,16854,16812],{},[18,16856,16857],{},"`void Map::naviFrom(QString from){",[18,16859,16860],{},"this->page()->mainFrame()->evaluateJavaScript(QString(\"route(\"%1\")\").arg(from));",[18,16862,16812],{},[18,16864,16865],{},"`void Map::openSynyx(){",[18,16867,16868],{},"this->page()->mainFrame()->evaluateJavaScript(\"openSynyx()\");",[18,16870,16812],{},[18,16872,16873],{},"mainscreen.cpp initializes the map with the path to the html file",[18,16875,16876],{},[45,16877,16878],{},"map->load(QUrl(\"./index.html\") ) ;",[18,16880,16881],{},"Now if you put all things together in an app, by adding a few buttons, a textfield and connect them to the Map::naviFrom\nmethod it could look like this:",[18,16883,16884],{},[1195,16885],{"alt":43,"src":16886},"https://media.synyx.de/uploads//2010/06/maps_navi.png",[18,16888,16889],{},"google maps in maemo 5",[18,16891,16892],{},"The evaluation of the position (coordinates, or town labels, street names etc.) and the rendering of the route will be\ndone by google’s api. So in contrast to other mobile plattforms with native libraries, you do not have to bother about\nthat.",[18,16894,16895],{},"Part 2 will describe a way how to use the internal gps device of your N900 and how to use callback mechanisms for\nabstraction of the position handling.",{"title":43,"searchDepth":57,"depth":57,"links":16897},[16898],{"id":16773,"depth":57,"text":16774},[6671,406],"2010-06-07T22:11:18","https://synyx.de/blog/google-maps-on-maemo-5-part-1/",{},"/blog/google-maps-on-maemo-5-part-1",{"title":16761,"description":16770},"blog/google-maps-on-maemo-5-part-1",[16907,14147,16908],"google-maps","qt-4-6","In this post i will show you how to realize a Maemo 5 Qt 4.6 application with google maps integration. The map: There is a good short tutorial with included…","4g6rav3KqWuFVlf1u80dnCPLsXJOJ9HW3KqsIkfGE_A",{"id":16912,"title":16913,"author":16914,"body":16915,"category":16995,"date":16996,"description":43,"extension":409,"link":16997,"meta":16998,"navigation":177,"path":16999,"seo":17000,"slug":17001,"stem":17002,"tags":17003,"teaser":17004,"__hash__":17005},"blog/blog/howto-startup-with-maemo-and-qt-4-6-step2-configure-ide.md","Howto startup with Maemo and Qt 4.6 – Step2: Configure your IDE",[14032],{"type":11,"value":16916,"toc":16988},[16917,16920,16924,16933,16936,16941,16944,16948,16951,16957,16960,16963,16967,16970,16974,16981,16985],[14,16918,16913],{"id":16919},"howto-startup-with-maemo-and-qt-46-step2-configure-your-ide",[1822,16921,16923],{"id":16922},"configuration-of-qt","Configuration of Qt:",[18,16925,16926,16927,16932],{},"My ",[22,16928,16931],{"href":16929,"rel":16930},"http://mobile.synyx.de/2010/04/howto-startup-with-maemo-and-qt-4-6/",[26],"last post"," described howto setup your sdk an\nide for Maemo5 and Qt development on the N900 devide. This time i will show you how to configure your ide and setup your\nfirst project.",[18,16934,16935],{},"First of all Goto Windows->Preferences_>Qt and add a new entry in the list. The Version needs a label. I chosed the\nversion of Qt. Then you have to add a few paths in the scratchbox environment where Qt and its helper programs are\nlocated. Have a look at the image below.",[18,16937,16938],{},[1195,16939],{"alt":43,"src":16940},"https://media.synyx.de/uploads//2010/06/setupqtCreator1.png",[18,16942,16943],{},"configure your QT version for Eclipse",[1822,16945,16947],{"id":16946},"check-the-installed-targets","Check the installed targets:",[18,16949,16950],{},"Goto Windows->Preferences->Maemo->Installed Targets. You should see an entry “scratchbox 1” with two subentries(\ntargets).",[18,16952,16953],{},[1195,16954],{"alt":16955,"src":16956},"\"your configuration should look like this\"","https://media.synyx.de/uploads//2010/06/setupScratchbox.png",[18,16958,16959],{},"your configuration should look like this",[18,16961,16962],{},"If it is not there hit the “Install” Button and select “Scratchbox1”. Complete the wizzard for the fremantle plattform.\nNext you may install the targets “FREMANTLE_X86″(your emulator) and “FREMANTLE_ARMEL” (for your real N900 device) by\nclicking on the “Install” button again and chosing “Scratchbox1” targets.",[1822,16964,16966],{"id":16965},"create-a-new-project","Create a new project:",[18,16968,16969],{},"In the wizard dialog chose “C++ Maemo Projekt” and click Next. A new Windows wil appear. Select the “QT Hello World”\ntemplate and click “Next”. In that dialog check all configurations if you want to build your code for the N900. Type in\nan appropriate project name and hit the “Finish” button.",[1822,16971,16973],{"id":16972},"correct-the-qt-paths","Correct the Qt paths:",[18,16975,16976,16977,16980],{},"It looks like eclipse does not use the same paths for the editor and the build process. To have autocorrection of your\nsource code and so on, you should check the includes. Goto project properties ->c/c++ Paths and Symbols->Includes.\nCheck the paths for the different configurations for the “GNU C++ language”. I had to remove the part ../",[974,16978,16979],{},"qt","/Qt…\nfrom the path.",[1822,16982,16984],{"id":16983},"congratulation","Congratulation:",[18,16986,16987],{},"You are done. Now you may add the perspective Qt C++ for visual editing. I will write a separate post for you how to\ndo that. Also remind that you have to add each gui mask, cpp file or header file to the Qt-Project file (.pro)\nmanually. Otherwise if you build a project the file will be not included result.",{"title":43,"searchDepth":57,"depth":57,"links":16989},[16990,16991,16992,16993,16994],{"id":16922,"depth":57,"text":16923},{"id":16946,"depth":57,"text":16947},{"id":16965,"depth":57,"text":16966},{"id":16972,"depth":57,"text":16973},{"id":16983,"depth":57,"text":16984},[6671,406],"2010-06-02T07:23:51","https://synyx.de/blog/howto-startup-with-maemo-and-qt-4-6-step2-configure-ide/",{},"/blog/howto-startup-with-maemo-and-qt-4-6-step2-configure-ide",{"title":16913,"description":43},"howto-startup-with-maemo-and-qt-4-6-step2-configure-ide","blog/howto-startup-with-maemo-and-qt-4-6-step2-configure-ide",[14147,16908],"Configuration of Qt: My last post described howto setup your sdk an ide for Maemo5 and Qt development on the N900 devide. This time i will show you how to…","F7fsk3eZ_jSnxKNx_wv8A5Bn_k8qXfFKoybzDO9DNYI",{"id":17007,"title":17008,"author":17009,"body":17010,"category":17246,"date":17247,"description":17248,"extension":409,"link":17249,"meta":17250,"navigation":177,"path":17251,"seo":17252,"slug":17014,"stem":17254,"tags":17255,"teaser":17257,"__hash__":17258},"blog/blog/performance-optimization-in-synyx-sudoku.md","Performance optimization in Synyx Sudoku",[6686],{"type":11,"value":17011,"toc":17244},[17012,17015,17024,17027,17030,17033,17036,17045,17048,17051,17054,17097,17100,17211,17214,17229,17236,17239,17242],[14,17013,17008],{"id":17014},"performance-optimization-in-synyx-sudoku",[18,17016,17017,17018,17023],{},"Now that ",[22,17019,17022],{"href":17020,"rel":17021},"http://mobile.synyx.de/2010/04/22/release-of-synyxsudoku",[26],"SynyxSudoku"," has been on the market a little while,\nI want to tell a little about what kind of measurements we took to optimize the performance of it.",[18,17025,17026],{},"First of all I have to say, that this was our first android project and I’m also just a first year trainee at the\nmoment, so there were quite a few issues, not only about the performance that had to be solved. I learned a lot in this\nproject and want to share a bit of what I learned with you, so I hope this will help you to improve the performance of\nyour apps, as well.",[18,17028,17029],{},"The biggest problem was probably the large number of views that the activity had at start (somewhat about 300…), so the\napp needed really long to start and didn’t run that fast because of it.",[18,17031,17032],{},"To track this kind of problem, google integrated the hierarchy viewer to the toolkit of android, which gives you an\noverview of all the currently loaded views in the selected app.",[18,17034,17035],{},"So it was soon clear, that the highscore was the big problem, because it was built as a table with as much rows as there\nwas data – what actually causes two different performance issues, first the big number of views and second the\ninefficient way of creating this views.",[18,17037,17038,17039,17044],{},"After a little bit of research we found the solution to this in the google I/O\nvideo ",[22,17040,17043],{"href":17041,"rel":17042},"http://www.youtube.com/watch?v=N6YdwzAvwOA",[26],"“Make your Android UI Fast and Efficient”",", which you really should\nwatch, if you haven’t by now!",[18,17046,17047],{},"So we found out that there’s a ListView element that takes care of such big lists as the highscore quite easily. The\ntrick to it is, that it only has that much views loaded, as there are visible to the user and it recycles them if the\nscroll out of the screen -> the views that scroll out of the screen are taken out of the list, have their data\nreplaced, and are put in on the opposite side again.",[18,17049,17050],{},"And that’s how it’s looking in the code:",[18,17052,17053],{},"First you have to overwrite the BaseAdapter from android so that you can give it your specific views to display:",[38,17055,17057],{"className":40,"code":17056,"language":42,"meta":43,"style":43},"public class HighscoreListAdapter extends BaseAdapter {\n private List valueList;\n private LayoutInflater inflater;\n public HighscoreListAdapter(List highscoreValueList, LayoutInflater inflater) {\n this.inflater = inflater;\n this.valueList = highscoreValueList;\n }\n}\n",[45,17058,17059,17064,17069,17074,17079,17084,17089,17093],{"__ignoreMap":43},[48,17060,17061],{"class":50,"line":51},[48,17062,17063],{},"public class HighscoreListAdapter extends BaseAdapter {\n",[48,17065,17066],{"class":50,"line":57},[48,17067,17068],{}," private List valueList;\n",[48,17070,17071],{"class":50,"line":63},[48,17072,17073],{}," private LayoutInflater inflater;\n",[48,17075,17076],{"class":50,"line":69},[48,17077,17078],{}," public HighscoreListAdapter(List highscoreValueList, LayoutInflater inflater) {\n",[48,17080,17081],{"class":50,"line":75},[48,17082,17083],{}," this.inflater = inflater;\n",[48,17085,17086],{"class":50,"line":81},[48,17087,17088],{}," this.valueList = highscoreValueList;\n",[48,17090,17091],{"class":50,"line":124},[48,17092,14385],{},[48,17094,17095],{"class":50,"line":129},[48,17096,266],{},[18,17098,17099],{},"and also overwrite the getView method from it, so that you can make your recycling in there:",[38,17101,17103],{"className":40,"code":17102,"language":42,"meta":43,"style":43},"public View getView(int position, View convertView, ViewGroup parent) {\n HighscoreViewHolder highscoreViewHolder;\n if (convertView == null) {\n // the first few elements of the list are created out of the xml\n convertView = inflater.inflate(R.layout.highscore_list_entry, null);\n highscoreViewHolder = new HighscoreViewHolder();\n highscoreViewHolder.name = (TextView) convertView.findViewById(R.id.highscore_list_entry_name);\n highscoreViewHolder.score = (TextView) convertView.findViewById(R.id.highscore_list_entry_score);\n highscoreViewHolder.rank = (TextView) convertView.findViewById(R.id.highscore_list_entry_rank);\n convertView.setTag(highscoreViewHolder);\n convertView.setFocusable(false);\n } else {\n // recycle of the view that went out of the view\n highscoreViewHolder = (HighscoreViewHolder) convertView.getTag();\n }\n HighscoreValue value = this.valueList.get(position);\n // set the new values\n highscoreViewHolder.name.setText(value.getName());\n highscoreViewHolder.score.setText(Integer.toString(value.getScore()));\n highscoreViewHolder.rank.setText(Integer.toString(value.getRank()));\n return convertView;\n}\n",[45,17104,17105,17109,17114,17119,17124,17129,17134,17139,17144,17149,17154,17159,17163,17168,17173,17177,17182,17187,17192,17197,17202,17207],{"__ignoreMap":43},[48,17106,17107],{"class":50,"line":51},[48,17108,11228],{},[48,17110,17111],{"class":50,"line":57},[48,17112,17113],{}," HighscoreViewHolder highscoreViewHolder;\n",[48,17115,17116],{"class":50,"line":63},[48,17117,17118],{}," if (convertView == null) {\n",[48,17120,17121],{"class":50,"line":69},[48,17122,17123],{}," // the first few elements of the list are created out of the xml\n",[48,17125,17126],{"class":50,"line":75},[48,17127,17128],{}," convertView = inflater.inflate(R.layout.highscore_list_entry, null);\n",[48,17130,17131],{"class":50,"line":81},[48,17132,17133],{}," highscoreViewHolder = new HighscoreViewHolder();\n",[48,17135,17136],{"class":50,"line":124},[48,17137,17138],{}," highscoreViewHolder.name = (TextView) convertView.findViewById(R.id.highscore_list_entry_name);\n",[48,17140,17141],{"class":50,"line":129},[48,17142,17143],{}," highscoreViewHolder.score = (TextView) convertView.findViewById(R.id.highscore_list_entry_score);\n",[48,17145,17146],{"class":50,"line":204},[48,17147,17148],{}," highscoreViewHolder.rank = (TextView) convertView.findViewById(R.id.highscore_list_entry_rank);\n",[48,17150,17151],{"class":50,"line":210},[48,17152,17153],{}," convertView.setTag(highscoreViewHolder);\n",[48,17155,17156],{"class":50,"line":216},[48,17157,17158],{}," convertView.setFocusable(false);\n",[48,17160,17161],{"class":50,"line":357},[48,17162,8636],{},[48,17164,17165],{"class":50,"line":363},[48,17166,17167],{}," // recycle of the view that went out of the view\n",[48,17169,17170],{"class":50,"line":369},[48,17171,17172],{}," highscoreViewHolder = (HighscoreViewHolder) convertView.getTag();\n",[48,17174,17175],{"class":50,"line":1978},[48,17176,14385],{},[48,17178,17179],{"class":50,"line":1991},[48,17180,17181],{}," HighscoreValue value = this.valueList.get(position);\n",[48,17183,17184],{"class":50,"line":2010},[48,17185,17186],{}," // set the new values\n",[48,17188,17189],{"class":50,"line":2023},[48,17190,17191],{}," highscoreViewHolder.name.setText(value.getName());\n",[48,17193,17194],{"class":50,"line":2036},[48,17195,17196],{}," highscoreViewHolder.score.setText(Integer.toString(value.getScore()));\n",[48,17198,17199],{"class":50,"line":2048},[48,17200,17201],{}," highscoreViewHolder.rank.setText(Integer.toString(value.getRank()));\n",[48,17203,17204],{"class":50,"line":2060},[48,17205,17206],{}," return convertView;\n",[48,17208,17209],{"class":50,"line":2073},[48,17210,266],{},[18,17212,17213],{},"now whats only left is to create the adapter, and assign it to the ListView:",[38,17215,17217],{"className":40,"code":17216,"language":42,"meta":43,"style":43},"highscoreListAdapter = new HighscoreListAdapter(highscoreList, getLayoutInflater());\nlistView.setAdapter(highscoreListAdapter);\n",[45,17218,17219,17224],{"__ignoreMap":43},[48,17220,17221],{"class":50,"line":51},[48,17222,17223],{},"highscoreListAdapter = new HighscoreListAdapter(highscoreList, getLayoutInflater());\n",[48,17225,17226],{"class":50,"line":57},[48,17227,17228],{},"listView.setAdapter(highscoreListAdapter);\n",[18,17230,17231,17232,17235],{},"If you make some changes in the data set you gave to the adapter, just call ",[275,17233,17234],{},"notifyDataSetChanged()"," on it to let it\nrefresh itself.",[18,17237,17238],{},"So after this change the app started remarkably faster and also ran faster then before, the views were cut to a merely\n120 in numbers.",[18,17240,17241],{},"Well, that’s it for the moment. If you want further advices regarding the performance of android apps, please watch the\nvideo I mentioned above. It helped me a lot and I’m sure it will also show you a few tricks how to make your app faster.",[394,17243,396],{},{"title":43,"searchDepth":57,"depth":57,"links":17245},[],[6671,406],"2010-05-28T08:06:05","Now that SynyxSudoku has been on the market a little while,\\nI want to tell a little about what kind of measurements we took to optimize the performance of it.","https://synyx.de/blog/performance-optimization-in-synyx-sudoku/",{},"/blog/performance-optimization-in-synyx-sudoku",{"title":17008,"description":17253},"Now that SynyxSudoku has been on the market a little while,\nI want to tell a little about what kind of measurements we took to optimize the performance of it.","blog/performance-optimization-in-synyx-sudoku",[7380,17256],"performance","Now that SynyxSudoku has been on the market a little while, I want to tell a little about what kind of measurements we took to optimize the performance of it.…","5S0ZY4kFZSda0-ABG8x4vCBcnZfT2QfTdDlf0aTxKYk",{"id":17260,"title":17261,"author":17262,"body":17263,"category":17550,"date":17551,"description":17552,"extension":409,"link":17553,"meta":17554,"navigation":177,"path":17555,"seo":17556,"slug":17267,"stem":17558,"tags":17559,"teaser":17560,"__hash__":17561},"blog/blog/how-to-add-a-find-your-company-feature-to-your-iphone-app-part-ii.md","How to add a “Find Your Company” feature to your iPhone App – Part II",[11319],{"type":11,"value":17264,"toc":17548},[17265,17268,17281,17284,17290,17293,17309,17475,17478,17483,17506,17513,17527,17534,17539,17546],[14,17266,17261],{"id":17267},"how-to-add-a-find-your-company-feature-to-your-iphone-app-part-ii",[18,17269,17270,17271,17276,17277,392],{},"In\nmy ",[22,17272,17275],{"href":17273,"rel":17274},"http://mobile.synyx.de/2010/05/06/how-to-add-a-find-your-company-feature-to-your-iphone-app-part-i/",[26],"first installment",",\nwe laid the foundation for todays blog post. So don’t hesitate to head back for a recap, if you need to. You can\ndownload the code for this tutorial on ",[22,17278,14488],{"href":17279,"rel":17280},"http://gist.github.com/388323/ea35c6d31270babb73ec052f1442c43afd6b5510",[26],[18,17282,17283],{},"If you start the App where we left off, you get the two pins, but it won’t be properly zoomed. In fact, it would look\nsomething like this:",[18,17285,17286],{},[1195,17287],{"alt":17288,"src":17289},"\"screen_synyx_map\"","https://media.synyx.de/uploads//2010/05/maps-3.png",[18,17291,17292],{},"As you can see, the pins are not really helpful at this zoom level and it’s definitely not user friendly, if you have to\nzoom in by yourself. What we need is a way to zoom in, so that the pin and the current location nicely fit on the\nscreen.",[18,17294,17295,17296,17299,17300,17304,17305,17308],{},"That’s exactly what the method ",[275,17297,17298],{},"centerMapAroundAnnotations"," at\nline ",[22,17301,17303],{"href":17279,"rel":17302},[26],"108"," of our ",[275,17306,17307],{},"UIViewController"," does:",[38,17310,17312],{"className":12276,"code":17311,"language":12278,"meta":43,"style":43},"\nif ( [[self.mapView annotations] count] \u003C 2 )\n return;\nCLLocationCoordinate2D min;\nCLLocationCoordinate2D max;\nBOOL minMaxInitialized = NO;\nfor ( id\u003CMKAnnotation> a in [self.mapView annotations] ) {\n if ( !minMaxInitialized ) {\n min = a.coordinate;\n max = a.coordinate;\n minMaxInitialized = YES;\n } else {\n min.latitude = MIN( min.latitude, a.coordinate.latitude );\n min.longitude = MIN( min.longitude, a.coordinate.longitude );\n max.latitude = MAX( max.latitude, a.coordinate.latitude );\n max.longitude = MAX( max.longitude, a.coordinate.longitude );\n }\n}\u003Cbr/>\nCLLocation* locSouthWest = [[CLLocation alloc] initWithLatitude: min.latitude longitude: min.longitude];\nCLLocation* locSouthEast = [[CLLocation alloc] initWithLatitude: min.latitude longitude: max.longitude];\nCLLocation* locNorthEast = [[CLLocation alloc] initWithLatitude: max.latitude longitude: max.longitude];\nCLLocationCoordinate2D regionCenter;\nregionCenter.latitude = (min.latitude + max.latitude) / 2.0;\nregionCenter.longitude = (min.longitude + max.longitude) / 2.0;\u003Cbr/>\nCLLocationDistance latMeters = [locSouthEast getDistanceFrom: locNorthEast];\nCLLocationDistance lonMeters = [locSouthEast getDistanceFrom: locSouthWest];\nMKCoordinateRegion region;\nregion = MKCoordinateRegionMakeWithDistance( regionCenter, latMeters, lonMeters );\nMKCoordinateRegion fitRegion = [self.mapView regionThatFits: region];\n[self.mapView setRegion: fitRegion animated: YES];\n[locSouthWest release];\n[locSouthEast release];\n[locNorthEast release];\n\n",[45,17313,17314,17318,17323,17327,17332,17337,17342,17347,17352,17357,17362,17367,17371,17376,17381,17386,17391,17395,17400,17405,17410,17415,17420,17425,17430,17435,17440,17445,17450,17455,17460,17465,17470],{"__ignoreMap":43},[48,17315,17316],{"class":50,"line":51},[48,17317,178],{"emptyLinePlaceholder":177},[48,17319,17320],{"class":50,"line":57},[48,17321,17322],{},"if ( [[self.mapView annotations] count] \u003C 2 )\n",[48,17324,17325],{"class":50,"line":63},[48,17326,14428],{},[48,17328,17329],{"class":50,"line":69},[48,17330,17331],{},"CLLocationCoordinate2D min;\n",[48,17333,17334],{"class":50,"line":75},[48,17335,17336],{},"CLLocationCoordinate2D max;\n",[48,17338,17339],{"class":50,"line":81},[48,17340,17341],{},"BOOL minMaxInitialized = NO;\n",[48,17343,17344],{"class":50,"line":124},[48,17345,17346],{},"for ( id\u003CMKAnnotation> a in [self.mapView annotations] ) {\n",[48,17348,17349],{"class":50,"line":129},[48,17350,17351],{}," if ( !minMaxInitialized ) {\n",[48,17353,17354],{"class":50,"line":204},[48,17355,17356],{}," min = a.coordinate;\n",[48,17358,17359],{"class":50,"line":210},[48,17360,17361],{}," max = a.coordinate;\n",[48,17363,17364],{"class":50,"line":216},[48,17365,17366],{}," minMaxInitialized = YES;\n",[48,17368,17369],{"class":50,"line":357},[48,17370,8636],{},[48,17372,17373],{"class":50,"line":363},[48,17374,17375],{}," min.latitude = MIN( min.latitude, a.coordinate.latitude );\n",[48,17377,17378],{"class":50,"line":369},[48,17379,17380],{}," min.longitude = MIN( min.longitude, a.coordinate.longitude );\n",[48,17382,17383],{"class":50,"line":1978},[48,17384,17385],{}," max.latitude = MAX( max.latitude, a.coordinate.latitude );\n",[48,17387,17388],{"class":50,"line":1991},[48,17389,17390],{}," max.longitude = MAX( max.longitude, a.coordinate.longitude );\n",[48,17392,17393],{"class":50,"line":2010},[48,17394,14385],{},[48,17396,17397],{"class":50,"line":2023},[48,17398,17399],{},"}\u003Cbr/>\n",[48,17401,17402],{"class":50,"line":2036},[48,17403,17404],{},"CLLocation* locSouthWest = [[CLLocation alloc] initWithLatitude: min.latitude longitude: min.longitude];\n",[48,17406,17407],{"class":50,"line":2048},[48,17408,17409],{},"CLLocation* locSouthEast = [[CLLocation alloc] initWithLatitude: min.latitude longitude: max.longitude];\n",[48,17411,17412],{"class":50,"line":2060},[48,17413,17414],{},"CLLocation* locNorthEast = [[CLLocation alloc] initWithLatitude: max.latitude longitude: max.longitude];\n",[48,17416,17417],{"class":50,"line":2073},[48,17418,17419],{},"CLLocationCoordinate2D regionCenter;\n",[48,17421,17422],{"class":50,"line":2081},[48,17423,17424],{},"regionCenter.latitude = (min.latitude + max.latitude) / 2.0;\n",[48,17426,17427],{"class":50,"line":2092},[48,17428,17429],{},"regionCenter.longitude = (min.longitude + max.longitude) / 2.0;\u003Cbr/>\n",[48,17431,17432],{"class":50,"line":2098},[48,17433,17434],{},"CLLocationDistance latMeters = [locSouthEast getDistanceFrom: locNorthEast];\n",[48,17436,17437],{"class":50,"line":2106},[48,17438,17439],{},"CLLocationDistance lonMeters = [locSouthEast getDistanceFrom: locSouthWest];\n",[48,17441,17442],{"class":50,"line":2112},[48,17443,17444],{},"MKCoordinateRegion region;\n",[48,17446,17447],{"class":50,"line":2117},[48,17448,17449],{},"region = MKCoordinateRegionMakeWithDistance( regionCenter, latMeters, lonMeters );\n",[48,17451,17452],{"class":50,"line":2129},[48,17453,17454],{},"MKCoordinateRegion fitRegion = [self.mapView regionThatFits: region];\n",[48,17456,17457],{"class":50,"line":2150},[48,17458,17459],{},"[self.mapView setRegion: fitRegion animated: YES];\n",[48,17461,17462],{"class":50,"line":2161},[48,17463,17464],{},"[locSouthWest release];\n",[48,17466,17467],{"class":50,"line":2173},[48,17468,17469],{},"[locSouthEast release];\n",[48,17471,17472],{"class":50,"line":2184},[48,17473,17474],{},"[locNorthEast release];\n",[18,17476,17477],{},"The code might look complicated, but we can break it down into 3 steps:",[10042,17479,17480],{},[871,17481,17482],{},"Iterate over all our custom annotations (we only have one) to determine the max/min latitude and longitude",[868,17484,17485,17499],{},[871,17486,17487,17488,744,17491,17494,17495,17498],{},"This results in a triangle ",[275,17489,17490],{},"locSouthWest",[275,17492,17493],{},"locSouthEast"," and ",[275,17496,17497],{},"locNorthEast",", that we can use to determine the center\nfor our zoom",[871,17500,17501,17502,17505],{},"Using the distance between the triangle points and the center, we can fit the map into a ",[275,17503,17504],{},"MKCoordinateRegion",", that\nwill zoom it so that everything fits on the screen.",[18,17507,17508,17509,17512],{},"One thing this algorithm doesn’t include, is the ",[275,17510,17511],{},"AnnotationView"," that display the name and location of the red pin. It\nsometimes happens, that it’s displayed outside of the screen, once you tap on the pin. However, if you add the code\nabove and hook it into the following method:",[38,17514,17516],{"className":12276,"code":17515,"language":12278,"meta":43,"style":43},"\n- (void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray *)views\n\n",[45,17517,17518,17522],{"__ignoreMap":43},[48,17519,17520],{"class":50,"line":51},[48,17521,178],{"emptyLinePlaceholder":177},[48,17523,17524],{"class":50,"line":57},[48,17525,17526],{},"- (void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray *)views\n",[18,17528,17529,17530,17533],{},"You still need to add the code, which handles the annotation, but you can feel free to steal it\nfrom ",[22,17531,14488],{"href":17279,"rel":17532},[26],". The result should look something\nlike this:",[18,17535,17536],{},[1195,17537],{"alt":17288,"src":17538},"https://media.synyx.de/uploads//2010/05/screen_synyx_map.png",[18,17540,17541,17542,17545],{},"This concludes our little tutorial on Google Maps and ",[275,17543,17544],{},"MapKit",". I hope it was helpful! If you need any assistance, just\nleave a comment or drop me an email, I’ll be happy to help you out.",[394,17547,396],{},{"title":43,"searchDepth":57,"depth":57,"links":17549},[],[6671,406],"2010-05-19T06:44:08","In\\nmy first installment,\\nwe laid the foundation for todays blog post. So don’t hesitate to head back for a recap, if you need to. You can\\ndownload the code for this tutorial on github.","https://synyx.de/blog/how-to-add-a-find-your-company-feature-to-your-iphone-app-part-ii/",{},"/blog/how-to-add-a-find-your-company-feature-to-your-iphone-app-part-ii",{"title":17261,"description":17557},"In\nmy first installment,\nwe laid the foundation for todays blog post. So don’t hesitate to head back for a recap, if you need to. You can\ndownload the code for this tutorial on github.","blog/how-to-add-a-find-your-company-feature-to-your-iphone-app-part-ii",[16907,11603],"In my first installment, we laid the foundation for todays blog post. So don’t hesitate to head back for a recap, if you need to. You can download the code…","Hpc7jZiUS-9fX42oCKNm3kaFp-aPqLQkR6HNXUf1bUU",{"id":17563,"title":17564,"author":17565,"body":17566,"category":17639,"date":17640,"description":17641,"extension":409,"link":17642,"meta":17643,"navigation":177,"path":17644,"seo":17645,"slug":17570,"stem":17647,"tags":17648,"teaser":17649,"__hash__":17650},"blog/blog/lessons-learned-iphone-review.md","Lessons learned – iPhone Review",[11319],{"type":11,"value":17567,"toc":17637},[17568,17571,17590,17634],[14,17569,17564],{"id":17570},"lessons-learned-iphone-review",[18,17572,17573,17574,10653,17579,10653,17584,17589],{},"When you submit an App to the Apple App Store it has to go through a “rigorous quality check”, conducted by Apple.\nAlthough ",[22,17575,17578],{"href":17576,"rel":17577},"http://apprejections.com/index.php/post/171",[26],"there are",[22,17580,17583],{"href":17581,"rel":17582},"http://www.mobileorchard.com/avoiding-iphone-app-rejection-from-apple/",[26],"plenty of resources",[22,17585,17588],{"href":17586,"rel":17587},"https://web.archive.org/web/20100809102557/http://iphone.derheckser.com:80/2009/07/10/suffering-from-modus-operandi-of-reviewer-team/",[26],"out there",",\nhere’s a short rundown of what we’ve learned ourselves so far:",[868,17591,17592,17598,17604,17610,17622],{},[871,17593,17594,17597],{},[974,17595,17596],{},"Marketing Apps are not allowed","\nIf the sole purpose of your App is marketing, you’ll have a hard time getting your App through. You need to add what\nApple calls “user functionality”. That could be something simple like a photo gallery or a feature to reserve a room\nor table.",[871,17599,17600,17603],{},[974,17601,17602],{},"You cannot tease your users with features that they have to pay for","\nIf you are offering a lite version of your App, you cannot add disabled functionality, which would be enabled in the\npaid version. A lite version usually is offered separately from a paid version, which means the user will constantly\nsee disabled menu items or buttons, since the App will never be updated. Instead add a info section about the paid\nversion in your App, which describes what the paid version offers.",[871,17605,17606,17609],{},[974,17607,17608],{},"Don’t ask your users to upgrade","\nYou cannot add an alert in a free/lite version of your App, which asks users to upgrade or buy the paid version.\nInstead you should add a “buy me” button or a section in your App further describing what your paid version offers.",[871,17611,17612,17615,17616,17621],{},[974,17613,17614],{},"Build a working App","\nYou are definitely rejected if your App is buggy! If the reviewer thinks he found a bug, he’ll reject your App. A\ncrash is the worst case scenario,\nbut ",[22,17617,17620],{"href":17618,"rel":17619},"http://dlinsin.blogspot.com/2010/05/don-forget-your-linker-flags.html",[26],"it happens",". However, don’t depend on\nApple as your QA, because the review times are too long to go back and forth this way.",[871,17623,17624,17627,17628,17633],{},[974,17625,17626],{},"Don’t infringe Trademarks or Copyrights","\nDon’t mention\nApple, ",[22,17629,17632],{"href":17630,"rel":17631},"http://www.geek.com/articles/mobile/apple-demands-a-developer-removes-android-references-from-iphone-app-2010024/",[26],"Android","\nor any other Trademark therefore – as long as you don’t own it. You should also resist to use iPhone like icons or\nimages.",[18,17635,17636],{},"If you respect all of these restrictions and gotchas, you should be save to get your App through the review process. I\nsay “should”, because it all appears to depend on the person who reviews your App. Let us know what you experienced,\nsubmitting your Apps, I bet there are a lot more of these gotchas.",{"title":43,"searchDepth":57,"depth":57,"links":17638},[],[6671,406],"2010-05-13T12:37:53","When you submit an App to the Apple App Store it has to go through a “rigorous quality check”, conducted by Apple.\\nAlthough there are plenty of resources out there,\\nhere’s a short rundown of what we’ve learned ourselves so far:","https://synyx.de/blog/lessons-learned-iphone-review/",{},"/blog/lessons-learned-iphone-review",{"title":17564,"description":17646},"When you submit an App to the Apple App Store it has to go through a “rigorous quality check”, conducted by Apple.\nAlthough there are plenty of resources out there,\nhere’s a short rundown of what we’ve learned ourselves so far:","blog/lessons-learned-iphone-review",[11601,11603],"When you submit an App to the Apple App Store it has to go through a “rigorous quality check”, conducted by Apple. Although there are plenty of resources out there,…","T4mUZEpPLjFmQlCw35HyM72gG1M41S4jYsCCinauV0A",{"id":17652,"title":17653,"author":17654,"body":17656,"category":17814,"date":17815,"description":17816,"extension":409,"link":17817,"meta":17818,"navigation":177,"path":17819,"seo":17820,"slug":17660,"stem":17822,"tags":17823,"teaser":17825,"__hash__":17826},"blog/blog/google-maps-on-android-part-2-overlays.md","Google Maps on Android – Part 2: Overlays",[17655],"heib",{"type":11,"value":17657,"toc":17812},[17658,17661,17670,17687,17690,17693,17696,17699,17702,17705,17708,17719,17733,17736,17739,17742,17744,17751,17754,17757,17773,17776,17779,17782,17784,17790],[14,17659,17653],{"id":17660},"google-maps-on-android-part-2-overlays",[18,17662,17663,17664,17669],{},"In my ",[22,17665,17668],{"href":17666,"rel":17667},"http://mobile.synyx.de/2010/04/30/google-maps-on-android/",[26],"last post about Google Maps on Android"," I showed you\nhow to use the basic navigation features of google maps, like moving the map to a defined area and zooming to a given\nlevel.",[18,17671,17672,17673,17678,17679,17682,17683,17686],{},"Now as you have centred your map and zoomed in, it would be a good idea to show some kind of marker. Otherwise the user\nwon’t actually realize what you want to show him. How a basic overlay is done, is described in\nthe ",[22,17674,17677],{"href":17675,"rel":17676},"http://developer.android.com/resources/tutorials/views/hello-mapview.html",[26],"tutorial on the android developer site",",\nalready mentioned in the ",[22,17680,16931],{"href":17666,"rel":17681},[26],". The\n",[275,17684,17685],{},"HelloItemizedOverlay"," that is created there, enables you to add as many markers to your map as you want:",[18,17688,17689],{},"`List mapOverlays = mapView.getOverlays();",[18,17691,17692],{},"Drawable drawable = this.getResources().getDrawable(R.drawable.synyxLogo);",[18,17694,17695],{},"HelloItemizedOverlay itemizedOverlay = new HelloItemizedOverlay(drawable);",[18,17697,17698],{},"OverlayItem synyxOfficeOverlay = new OverlayItem(synyxOfficeLocation, \"Synyx\", \"This is the Synyx office!\");",[18,17700,17701],{},"itemizedOverlay.addOverlay(synyxOfficeOverlay);",[18,17703,17704],{},"// add more overlayItems...",[18,17706,17707],{},"mapOverlays.add(itemizedOverlay);`",[18,17709,17710,17711,17714,17715,17718],{},"This will draw a nice marker just at the location of the synyx office. The drawback of this method is, that all markers\non the map just look the same. You might think: No problem – just use the ",[275,17712,17713],{},"setMarker()"," method of the ",[275,17716,17717],{},"OverlayItem"," to\nset a new marker for just this item. As this looks like the way to do it, it won’t work. All you get is no marker (not\nthe one you just set, nor the default one). The map is just shown as if your marker was not defined at all.",[18,17720,17721,17722,17725,17726,17729,17730,17732],{},"So how to do it? The trick is the static method ",[275,17723,17724],{},"boundCenterBottom()"," of the ",[275,17727,17728],{},"ItemizedOverlay"," class. This method is\nalso called in the constructor of the ",[275,17731,17685],{},", when the default marker is set:",[18,17734,17735],{},"`public RouteMapOverlay(Drawable defaultMarker, Context context) {",[18,17737,17738],{},"super(boundCenterBottom(defaultMarker));",[18,17740,17741],{},"this.context = context;",[18,17743,16812],{},[18,17745,17746,17747,17750],{},"The problem is, that the visibility of this method is set to ",[275,17748,17749],{},"protected"," (why?!). So calling it when setting the marker\nwon’t work:",[18,17752,17753],{},"`// won't work as boundCenterBottom is protected",[18,17755,17756],{},"synyxOfficeOverlay.setOverlay(ItemizedOverlay.boundCenterBottom(myNewMarker));`",[18,17758,17759,17760,17762,17763,17765,17766,17769,17770,17772],{},"Because of this visibility feature, the “easier” way is to add a new setter to your ",[275,17761,17685],{}," class,\naccepting an ",[275,17764,17717],{}," and a ",[275,17767,17768],{},"Drawable"," to use as the marker. Within that setter, you are able to call the\n",[275,17771,17724],{}," method to set up the marker correctly:",[18,17774,17775],{},"`public void addOverlay(OverlayItem overlayItem, Drawable marker) {",[18,17777,17778],{},"overlayItem.setMarker(boundCenterBottom(marker));",[18,17780,17781],{},"addOverlay(overlayItem);",[18,17783,16812],{},[18,17785,17786,17787,17789],{},"Now if you use that setter, the map will correctly show your ",[275,17788,17717],{}," with your marker.",[18,17791,17792,17793,17795,17796,17798,17799,17802,17803,17806,17807,17809,17810,392],{},"Btw: instead of calling ",[275,17794,17724],{}," (which places the bottom centre of your ",[275,17797,17768],{}," over the defined\n",[275,17800,17801],{},"GeoPoint","), you can also use ",[275,17804,17805],{},"boundCenter()"," to place the centre of your ",[275,17808,17768],{}," over the ",[275,17811,17801],{},{"title":43,"searchDepth":57,"depth":57,"links":17813},[],[6671,406],"2010-05-07T16:52:13","In my last post about Google Maps on Android I showed you\\nhow to use the basic navigation features of google maps, like moving the map to a defined area and zooming to a given\\nlevel.","https://synyx.de/blog/google-maps-on-android-part-2-overlays/",{},"/blog/google-maps-on-android-part-2-overlays",{"title":17653,"description":17821},"In my last post about Google Maps on Android I showed you\nhow to use the basic navigation features of google maps, like moving the map to a defined area and zooming to a given\nlevel.","blog/google-maps-on-android-part-2-overlays",[7380,16907,17824,16262],"marker","In my last post about Google Maps on Android I showed you how to use the basic navigation features of google maps, like moving the map to a defined area…","GBZ-bHS5o5wBdJ9oZTRvsybQjazx-suQybxkfoerVPM",{"id":17828,"title":17829,"author":17830,"body":17831,"category":18032,"date":18033,"description":18034,"extension":409,"link":18035,"meta":18036,"navigation":177,"path":18037,"seo":18038,"slug":17835,"stem":18040,"tags":18041,"teaser":18042,"__hash__":18043},"blog/blog/how-to-add-a-find-your-company-feature-to-your-iphone-app-part-i.md","How to add a 'Find Your Company' feature to your iPhone App – Part I",[11319],{"type":11,"value":17832,"toc":18030},[17833,17837,17849,17856,17865,17868,17876,17892,17904,17957,17972,18006,18025,18028],[14,17834,17836],{"id":17835},"how-to-add-a-find-your-company-feature-to-your-iphone-app-part-i","How to add a \"Find Your Company\" feature to your iPhone App – Part I",[18,17838,17839,17840,17842,17843,17848],{},"We wanted to give our users the possibility to find our office. On the iPhone, the simplest way to do it, is to use\nGoogle Maps and the ",[275,17841,17544],{}," framework. I won’t go into the details of MapKit here, since Apple’s documentation is\nawesome and they provide a lot\nof ",[22,17844,17847],{"href":17845,"rel":17846},"http://developer.apple.com/iphone/library/samplecode/CurrentAddress/Introduction/Intro.html#//apple_ref/doc/uid/DTS40009469",[26],"sample code",",\nwhich gets you up and running in no time.",[18,17850,17851,17852,17855],{},"What I’d like to show you today, is some code, which nicely zooms the Map to your office and current location of the App\nuser, once the ",[275,17853,17854],{},"MapView"," is loaded. Here are a couple of screenshots to give you an idea of what I’m talking about:",[18,17857,17858,10653,17860,17864],{},[1195,17859],{"alt":17288,"src":17538},[1195,17861],{"alt":17862,"src":17863},"\"Synyx Routing\"","https://media.synyx.de/uploads//2010/05/screen_synyx_map_1.png","\nAs you can see, Synyx moved their offices to San Francisco. The iPhone App user, with the blue dot, is currently in\ncupertino. If he taps on the white/blue arrow in the annotation of the red dot, he is asked whether he’d really want to\nleave the App and start Google Maps to route from his current location to the Synyx Offices in San Francisco.",[18,17866,17867],{},"Now, the main focus on this blog post is how to get you using this in your App. I’m gonna split this little tutorial in\n2 parts:",[10042,17869,17870,17873],{},[871,17871,17872],{},"Showing the Synyx Offices on the Map",[871,17874,17875],{},"Zooming in, so that everything fits on the screen",[18,17877,17878,17879,17881,17882,17885,17886,17891],{},"If you feel like going off on your own, the code for the ",[275,17880,17307],{}," used, is available for download\non ",[22,17883,14488],{"href":17279,"rel":17884},[26],". The zooming algorithm is borrowed\nfrom ",[22,17887,17890],{"href":17888,"rel":17889},"http://stackoverflow.com/questions/1303265/algorithm-for-determining-minimum-bounding-rectangle-for-collection-of-lat-lon-co/1413264#1413264",[26],"stackoverflow",".\nHowever, the code is one of my first iPhone projects and not polished, so don’t use it blindly. There are a couple of\nissue, e.g. I’m pretty sure it won’t survive a memory warning. It’s meant for demonstration solely!",[18,17893,17894,17895,17897,17898,17900,17901,17903],{},"So let’s get into it. We first need a ",[275,17896,17307],{}," with a ",[275,17899,17854],{}," associated. It is pretty straightforward and\nyou should have no problems doing that in Interface Builder. The next thing you need and that is missing from the code\non github is a custom ",[275,17902,17544],{}," annotation:",[38,17905,17907],{"className":12276,"code":17906,"language":12278,"meta":43,"style":43},"\n@interface AddressAnnotation : NSObject\u003CMKAnnotation> {\n CLLocationCoordinate2D coordinate;\n NSString *title;\n NSString *subtitle;\n}\n- (id)initWith:(CLLocationCoordinate2D)_coords;\n@property(retain,nonatomic) NSString *title;\n@property(retain,nonatomic) NSString *subtitle;\n@end\n\n",[45,17908,17909,17913,17918,17923,17928,17933,17937,17942,17947,17952],{"__ignoreMap":43},[48,17910,17911],{"class":50,"line":51},[48,17912,178],{"emptyLinePlaceholder":177},[48,17914,17915],{"class":50,"line":57},[48,17916,17917],{},"@interface AddressAnnotation : NSObject\u003CMKAnnotation> {\n",[48,17919,17920],{"class":50,"line":63},[48,17921,17922],{}," CLLocationCoordinate2D coordinate;\n",[48,17924,17925],{"class":50,"line":69},[48,17926,17927],{}," NSString *title;\n",[48,17929,17930],{"class":50,"line":75},[48,17931,17932],{}," NSString *subtitle;\n",[48,17934,17935],{"class":50,"line":81},[48,17936,266],{},[48,17938,17939],{"class":50,"line":124},[48,17940,17941],{},"- (id)initWith:(CLLocationCoordinate2D)_coords;\n",[48,17943,17944],{"class":50,"line":129},[48,17945,17946],{},"@property(retain,nonatomic) NSString *title;\n",[48,17948,17949],{"class":50,"line":204},[48,17950,17951],{},"@property(retain,nonatomic) NSString *subtitle;\n",[48,17953,17954],{"class":50,"line":210},[48,17955,17956],{},"@end\n",[18,17958,17959,17960,17964,17965,17968,17969,392],{},"This will be responsible for displaying the name and city of the Synyx Offices above the red pin on the map. But it’s\nonly responsible for displaying the information, it doesn’t know where yet. In order to find the location of our offices\nusing the Google Maps API, I came up with a little helper method\ncalled ",[22,17961,17963],{"href":17279,"rel":17962},[26],"synyxLocation (line 175)",". It simply\nreturns the ",[275,17966,17967],{},"CLLocationCoordinate2D"," struct, which is needed in our ",[275,17970,17971],{},"AddressAnnotation",[38,17973,17975],{"className":12276,"code":17974,"language":12278,"meta":43,"style":43},"\nCLLocationCoordinate2D location = [self synyxLocation];\nself.synyx = [[AddressAnnotation alloc] initWith:location];\n[self.synyx setTitle:@\"Synyx GmbH & Co. KG\"];\n[self.synyx setSubtitle:synyxLoc];\n[mapView addAnnotation:synyx];\n\n",[45,17976,17977,17981,17986,17991,17996,18001],{"__ignoreMap":43},[48,17978,17979],{"class":50,"line":51},[48,17980,178],{"emptyLinePlaceholder":177},[48,17982,17983],{"class":50,"line":57},[48,17984,17985],{},"CLLocationCoordinate2D location = [self synyxLocation];\n",[48,17987,17988],{"class":50,"line":63},[48,17989,17990],{},"self.synyx = [[AddressAnnotation alloc] initWith:location];\n",[48,17992,17993],{"class":50,"line":69},[48,17994,17995],{},"[self.synyx setTitle:@\"Synyx GmbH & Co. KG\"];\n",[48,17997,17998],{"class":50,"line":75},[48,17999,18000],{},"[self.synyx setSubtitle:synyxLoc];\n",[48,18002,18003],{"class":50,"line":81},[48,18004,18005],{},"[mapView addAnnotation:synyx];\n",[18,18007,18008,18009,18011,18012,18014,18015,17725,18018,18020,18021,18024],{},"Instantiating the ",[275,18010,17971],{}," with the previously determined location and adding it to the ",[275,18013,17854],{}," will to the\nrest. I did this in the ",[275,18016,18017],{},"viewDidLoad",[275,18019,17307],{},", which is not be the best place. Maybe the\n",[275,18022,18023],{},"viewWillAppear"," method would have been better.",[18,18026,18027],{},"After adding those parts discussed above, you can fire up the Simulator and get the location of our offices with a red\npin and your current location (in the Simulator it’s always Cupertino). The map doesn’t zoom yet and is not properly\nlocated, we leave that to our next installment.",[394,18029,396],{},{"title":43,"searchDepth":57,"depth":57,"links":18031},[],[6671,406],"2010-05-06T09:19:07","We wanted to give our users the possibility to find our office. On the iPhone, the simplest way to do it, is to use\\nGoogle Maps and the MapKit framework. I won’t go into the details of MapKit here, since Apple’s documentation is\\nawesome and they provide a lot\\nof sample code,\\nwhich gets you up and running in no time.","https://synyx.de/blog/how-to-add-a-find-your-company-feature-to-your-iphone-app-part-i/",{},"/blog/how-to-add-a-find-your-company-feature-to-your-iphone-app-part-i",{"title":17829,"description":18039},"We wanted to give our users the possibility to find our office. On the iPhone, the simplest way to do it, is to use\nGoogle Maps and the MapKit framework. I won’t go into the details of MapKit here, since Apple’s documentation is\nawesome and they provide a lot\nof sample code,\nwhich gets you up and running in no time.","blog/how-to-add-a-find-your-company-feature-to-your-iphone-app-part-i",[16907,11603],"We wanted to give our users the possibility to find our office. On the iPhone, the simplest way to do it, is to use Google Maps and the MapKit framework.…","xf7CAeTBsMxG4WUKphnA4aL0RIh0fwQNjjYZpLpVrbw",{"id":18045,"title":18046,"author":18047,"body":18048,"category":18119,"date":18120,"description":18121,"extension":409,"link":18122,"meta":18123,"navigation":177,"path":18124,"seo":18125,"slug":18127,"stem":18128,"tags":18129,"teaser":18131,"__hash__":18132},"blog/blog/google-maps-on-android.md","Google Maps on Android – Part 1: Navigation",[17655],{"type":11,"value":18049,"toc":18117},[18050,18053,18059,18071,18076,18088,18091,18094,18097,18100],[14,18051,18046],{"id":18052},"google-maps-on-android-part-1-navigation",[18,18054,18055,18056,392],{},"Integrating a google map on android is quiet simple – how to do this basically is shown in\nthe ",[22,18057,17677],{"href":17675,"rel":18058},[26],[18,18060,18061,18062,18067,18068,18070],{},"Showing the map itself is one part, but most likely you want to interact with it in some way. The default map is\ncompletely zoomed out and centred somewhere over America. As this gives you a good idea how America looks like, it is\nnot very useful for showing the location of the Synyx office. So what we need to do, is to move the map to some point\nand zoom in to a certain level. First, lets get the coordinates for the Synyx office. This can easily be done by using\ngoogle maps: Navigate to ",[22,18063,18066],{"href":18064,"rel":18065},"http://maps.google.com",[26],"maps.google.com",", click on the “New” link on the top right and\nactivate the “LatLng Marker” from the Google Maps Labs. This adds an option to the context menu to drop a marker that\nshows the latitude/ longitude values. Use those values to create a new ",[275,18069,17801],{}," in your Android app:",[18,18072,18073],{},[45,18074,18075],{},"GeoPoint synyxOfficeLocation = new GeoPoint(49002175, 8394160);",[18,18077,18078,18079,18081,18082,18084,18085,3723],{},"As the ",[275,18080,17801],{}," handles its values in microdegrees, the values obtained from google maps must be multiplied with 100 000. Now as we have the GeoPoint, we need to centre the map to it and zoom in to a certain level. To perform this\ntasks, each ",[275,18083,17854],{}," has a ",[275,18086,18087],{},"MapController",[18,18089,18090],{},"`MapController mapController = mapView.getController();",[18,18092,18093],{},"mapController.setCenter(synyxOfficeLocation);",[18,18095,18096],{},"mapController.setZoom(20);`",[18,18098,18099],{},"This centres the map to the Synyx office. The zoom level must be a value between 1 (fully zoomed out) and 21 (fully\nzoomed in), so the value of 20 is already quiet close. Please note, that the highest zoom levels might not be available\nfor all areas.",[18,18101,18102,18103,1869,18106,17494,18109,18112,18113,18116],{},"The above mentioned methods change the map in a very static way. For some more visual effects try ",[275,18104,18105],{},"animateTo()",[275,18107,18108],{},"zoomIn()",[275,18110,18111],{},"zoomOut()"," to change the view of the map by showing a short animation. An also very helpful method is\n",[275,18114,18115],{},"zoomToSpan()"," which lets you define a latitude and longitude span that should be visible on the map – very handy if you\nwant to ensure that two or more points are visible to the user on the map.",{"title":43,"searchDepth":57,"depth":57,"links":18118},[],[6671,406],"2010-04-30T12:42:54","Integrating a google map on android is quiet simple – how to do this basically is shown in\\nthe tutorial on the android developer site.","https://synyx.de/blog/google-maps-on-android/",{},"/blog/google-maps-on-android",{"title":18046,"description":18126},"Integrating a google map on android is quiet simple – how to do this basically is shown in\nthe tutorial on the android developer site.","google-maps-on-android","blog/google-maps-on-android",[7380,16907,18130],"interaction","Integrating a google map on android is quiet simple – how to do this basically is shown in the tutorial on the android developer site. Showing the map itself is…","M12KhufnwFTj7q3RUZch3BzzQs8S7YaCZwbJ9q7ROfs",{"id":18134,"title":18135,"author":18136,"body":18137,"category":18189,"date":18190,"description":18191,"extension":409,"link":18192,"meta":18193,"navigation":177,"path":18194,"seo":18195,"slug":18196,"stem":18197,"tags":18198,"teaser":18199,"__hash__":18200},"blog/blog/howto-startup-with-maemo-and-qt-4-6.md","Howto startup with Maemo and Qt 4.6 – Step1: Setup",[14032],{"type":11,"value":18138,"toc":18187},[18139,18142,18145,18148,18151,18154,18157,18164,18171,18178],[14,18140,18135],{"id":18141},"howto-startup-with-maemo-and-qt-46-step1-setup",[18,18143,18144],{},"As a Java developer it was not easy for me to find the right entry point for developing c++, using the trend-setting\nQt 4.6 environment and having a cute ide with rapid prototyping capabilities. After a little bit of reading and lots of\ntrials and errors i found a way for me that worked.",[18,18146,18147],{},"First of all, if you use Kubuntu 10.04 like me, you have to edit your /etc/sysctl.conf and add the following line,\notherwise the maemo-sdk installer will fail to install:",[18,18149,18150],{},"`#scratbox maemo sdk fix",[18,18152,18153],{},"vm.mmap_min_addr = 0`",[18,18155,18156],{},"I installed the following tools in the given order:",[18,18158,18159,18160],{},"SDK: For the N900 Maemo device a scratchbox with additional Nokia binaries is freely available. I Installed scratchbox\nand the nokia sdk by following the instructions on\nmaemo.org: ",[22,18161,18162],{"href":18162,"rel":18163},"http://wiki.maemo.org/Documentation/Maemo_5_Final_SDK_Installation",[26],[18,18165,18166,18167],{},"IDE: esbox is a complete IDE based on Eclipse. With esbox you are able to develop, run your code in an “emulator” or on\nyour mobile device, and build complete .deb packages. The completely packaged archived did it for me. Download and unzip\nthe archive to any location that you like: ",[22,18168,18169],{"href":18169,"rel":18170},"http://esbox.garage.maemo.org/2nd_edition/installation_product.html",[26],[18,18172,18173,18174],{},"Qt IDE integration: a graphical ui designer is available. With Qt Designer Eclipse Integration you are able to drag and\ndrop Qt widgets to your ui and preview your masks. Although Qt Designer is not specially developed for Maemo, it does a\ngood job and you get good and fast results. Just download the archive and copy the content into the plugin directory of\nyour esbox\ninstallation : ",[22,18175,18176],{"href":18176,"rel":18177},"http://qt.nokia.com/developer/eclipse-integration",[26],[18,18179,18180,18181,18186],{},"In the ",[22,18182,18185],{"href":18183,"rel":18184},"http://mobile.synyx.de/2010/06/howto-startup-with-maemo-and-qt-4-6-%E2%80%93-step2-configure-ide/",[26],"next post"," i will\ndescribe howto prepare your ide for Qt developing.",{"title":43,"searchDepth":57,"depth":57,"links":18188},[],[6671,406],"2010-04-22T14:29:47","As a Java developer it was not easy for me to find the right entry point for developing c++, using the trend-setting\\nQt 4.6 environment and having a cute ide with rapid prototyping capabilities. After a little bit of reading and lots of\\ntrials and errors i found a way for me that worked.","https://synyx.de/blog/howto-startup-with-maemo-and-qt-4-6/",{},"/blog/howto-startup-with-maemo-and-qt-4-6",{"title":18135,"description":18144},"howto-startup-with-maemo-and-qt-4-6","blog/howto-startup-with-maemo-and-qt-4-6",[14147,16908],"As a Java developer it was not easy for me to find the right entry point for developing c++, using the trend-setting Qt 4.6 environment and having a cute ide…","jDT6Llv4Nx1vlsdPC2Q8Jn5pDBFQAkRE2f8V-pqh1Cw",[18202,18204,18207,18210,18213,18216,18219,18222,18225,18228,18231,18234,18237,18240,18243,18246,18249,18252,18255,18258,18261,18264,18266,18269,18272,18275,18278,18280,18282,18285,18288,18291,18294,18297,18300,18303,18305,18308,18310,18313,18316,18319,18322,18325,18327,18330,18333,18336,18339,18342,18345,18348,18351,18354,18357,18360,18363,18366,18369,18372,18375,18378,18381,18383,18386,18389,18392,18395,18397,18400,18403,18406,18409,18412,18414,18417,18420,18423,18426,18429,18432,18435,18438,18441,18444,18447,18450,18453,18456,18459,18462,18465,18468,18471,18474,18477,18480,18483,18486,18488,18491,18493,18496,18499,18501,18504,18507,18510,18513,18516,18519,18522,18524,18527,18530,18533,18536,18539,18542,18545,18548,18551,18554,18557,18560,18563,18566,18569,18572,18575,18576,18579,18582,18585,18588,18591,18594,18596,18599,18602,18605,18608],{"slug":1180,"name":18203},"Jennifer Abel",{"slug":18205,"name":18206},"allmendinger","Otto Allmendinger",{"slug":18208,"name":18209},"antony","Ben Antony",{"slug":18211,"name":18212},"arrasz","Joachim Arrasz",{"slug":18214,"name":18215},"bauer","David Bauer",{"slug":18217,"name":18218},"bechtold","Janine Bechtold",{"slug":18220,"name":18221},"boersig","Jasmin Börsig",{"slug":18223,"name":18224},"buch","Fabian Buch",{"slug":18226,"name":18227},"buchloh","Aljona Buchloh",{"slug":18229,"name":18230},"burgard","Julia Burgard",{"slug":18232,"name":18233},"caspar-schwedes","Caspar Schwedes",{"slug":18235,"name":18236},"christina-schmitt","Christina Schmitt",{"slug":18238,"name":18239},"clausen","Michael Clausen",{"slug":18241,"name":18242},"contargo_poetzsch","Thomas Pötzsch",{"slug":18244,"name":18245},"damrath","Sebastian Damrath",{"slug":18247,"name":18248},"daniel","Markus Daniel",{"slug":18250,"name":18251},"dasch","Julia Dasch",{"slug":18253,"name":18254},"denman","Joffrey Denman",{"slug":18256,"name":18257},"dfuchs","Daniel Fuchs",{"slug":18259,"name":18260},"dobler","Max Dobler",{"slug":18262,"name":18263},"dobriakov","Vladimir Dobriakov",{"slug":18265,"name":18265},"dreiqbik",{"slug":18267,"name":18268},"dschaefer","Denise Schäfer",{"slug":18270,"name":18271},"dschneider","Dominik Schneider",{"slug":18273,"name":18274},"duerlich","Isabell Duerlich",{"slug":18276,"name":18277},"dutkowski","Bernd Dutkowski",{"slug":18279,"name":18279},"eifler",{"slug":9,"name":18281},"Tim Essig",{"slug":18283,"name":18284},"ferstl","Maximilian Ferstl",{"slug":18286,"name":18287},"fey","Prisca Fey",{"slug":18289,"name":18290},"frank","Leonard Frank",{"slug":18292,"name":18293},"franke","Arnold Franke",{"slug":18295,"name":18296},"frischer","Nicolette Rudmann",{"slug":18298,"name":18299},"fuchs","Petra Fuchs",{"slug":18301,"name":18302},"gari","Sarah Gari",{"slug":14032,"name":18304},"Gast",{"slug":18306,"name":18307},"graf","Johannes Graf",{"slug":5356,"name":18309},"Daniela Grammlich",{"slug":18311,"name":18312},"guthardt","Sabrina Guthardt",{"slug":18314,"name":18315},"haeussler","Johannes Häussler",{"slug":18317,"name":18318},"hammann","Daniel Hammann",{"slug":18320,"name":18321},"heetel","Julian Heetel",{"slug":18323,"name":18324},"heft","Florian Heft",{"slug":17655,"name":18326},"Sebastian Heib",{"slug":18328,"name":18329},"heisler","Ida Heisler",{"slug":18331,"name":18332},"helm","Patrick Helm",{"slug":18334,"name":18335},"herbold","Michael Herbold",{"slug":18337,"name":18338},"hofmann","Peter Hofmann",{"slug":18340,"name":18341},"hopf","Florian Hopf",{"slug":18343,"name":18344},"jaud","Alina Jaud",{"slug":18346,"name":18347},"jayasinghe","Robin De Silva Jayasinghe",{"slug":18349,"name":18350},"jbuch","Jonathan Buch",{"slug":18352,"name":18353},"junghanss","Gitta Junghanß",{"slug":18355,"name":18356},"kadyietska","Khrystyna Kadyietska",{"slug":18358,"name":18359},"kannegiesser","Marc Kannegiesser",{"slug":18361,"name":18362},"karoly","Robert Károly",{"slug":18364,"name":18365},"karrasz","Katja Arrasz-Schepanski",{"slug":18367,"name":18368},"kaufmann","Florian Kaufmann",{"slug":18370,"name":18371},"kesler","Mike Kesler",{"slug":18373,"name":18374},"kirchgaessner","Bettina Kirchgäßner",{"slug":18376,"name":18377},"klem","Yannic Klem",{"slug":18379,"name":18380},"klenk","Timo Klenk",{"slug":6686,"name":18382},"Tobias Knell",{"slug":18384,"name":18385},"knoll","Anna-Lena Knoll",{"slug":18387,"name":18388},"knorre","Matthias Knorre",{"slug":18390,"name":18391},"koenig","Melanie König",{"slug":18393,"name":18394},"kraft","Thomas Kraft",{"slug":11612,"name":18396},"Florian Krupicka",{"slug":18398,"name":18399},"kuehn","Christian Kühn",{"slug":18401,"name":18402},"lange","Christian Lange",{"slug":18404,"name":18405},"larrasz","Luca Arrasz",{"slug":18407,"name":18408},"leist","Sascha Leist",{"slug":18410,"name":18411},"lihs","Michael Lihs",{"slug":11319,"name":18413},"David Linsin",{"slug":18415,"name":18416},"maniyar","Christian Maniyar",{"slug":18418,"name":18419},"martin","Björnie",{"slug":18421,"name":18422},"martin-koch","Martin Koch",{"slug":18424,"name":18425},"matt","Tobias Matt",{"slug":18427,"name":18428},"mennerich","Christian Mennerich",{"slug":18430,"name":18431},"menz","Alexander Menz",{"slug":18433,"name":18434},"meseck","Frederick Meseck",{"slug":18436,"name":18437},"messner","Oliver Messner",{"slug":18439,"name":18440},"michael-ploed","Michael Plöd",{"slug":18442,"name":18443},"mies","Marius Mies",{"slug":18445,"name":18446},"mihai","Alina Mihai",{"slug":18448,"name":18449},"moeller","Jörg Möller",{"slug":18451,"name":18452},"mohr","Rebecca Mohr",{"slug":18454,"name":18455},"moretti","David Moretti",{"slug":18457,"name":18458},"mueller","Sven Müller",{"slug":18460,"name":18461},"muessig","Alexander Müssig",{"slug":18463,"name":18464},"neupokoev","Grigory Neupokoev",{"slug":18466,"name":18467},"nussbaecher","Carmen Nussbächer",{"slug":18469,"name":18470},"ochs","Pascal Ochs",{"slug":18472,"name":18473},"oelhoff","Jan Oelhoff",{"slug":18475,"name":18476},"oengel","Yasin Öngel",{"slug":18478,"name":18479},"oezsoy","Enis Özsoy",{"slug":18481,"name":18482},"posch","Maya Posch",{"slug":18484,"name":18485},"ralfmueller","Ralf Müller",{"slug":18487,"name":18487},"redakteur",{"slug":18489,"name":18490},"reich","Michael Reich",{"slug":5858,"name":18492},"Karl-Ludwig Reinhard",{"slug":18494,"name":18495},"rmueller","Rebecca Müller",{"slug":18497,"name":18498},"rosum","Jan Rosum",{"slug":18500,"name":18500},"rueckert",{"slug":18502,"name":18503},"ruessel","Sascha Rüssel",{"slug":18505,"name":18506},"sauter","Moritz Sauter",{"slug":18508,"name":18509},"schaefer","Julian Schäfer",{"slug":18511,"name":18512},"scherer","Petra Scherer",{"slug":18514,"name":18515},"schlicht","Anne Schlicht",{"slug":18517,"name":18518},"schmidt","Jürgen Schmidt",{"slug":18520,"name":18521},"schneider","Tobias Schneider",{"slug":10097,"name":18523},"Benjamin Seber",{"slug":18525,"name":18526},"sommer","Marc Sommer",{"slug":18528,"name":18529},"speaker-fels","Jakob Fels",{"slug":18531,"name":18532},"speaker-gierke","Oliver Gierke",{"slug":18534,"name":18535},"speaker-krupa","Malte Krupa",{"slug":18537,"name":18538},"speaker-mader","Jochen Mader",{"slug":18540,"name":18541},"speaker-meusel","Tim Meusel",{"slug":18543,"name":18544},"speaker-milke","Oliver Milke",{"slug":18546,"name":18547},"speaker-paluch","Mark Paluch",{"slug":18549,"name":18550},"speaker-schad","Jörg Schad",{"slug":18552,"name":18553},"speaker-schalanda","Jochen Schalanda",{"slug":18555,"name":18556},"speaker-schauder","Jens Schauder",{"slug":18558,"name":18559},"speaker-unterstein","Johannes Unterstein",{"slug":18561,"name":18562},"speaker-wolff","Eberhard Wolff",{"slug":18564,"name":18565},"speaker-zoerner","Stefan Zörner",{"slug":18567,"name":18568},"stefan-belger","Stefan Belger",{"slug":18570,"name":18571},"steinegger","Roland Steinegger",{"slug":18573,"name":18574},"stern","sternchen synyx",{"slug":6609,"name":6609},{"slug":18577,"name":18578},"szulc","Mateusz Szulc",{"slug":18580,"name":18581},"tamara","Tamara Tunczinger",{"slug":18583,"name":18584},"theuer","Tobias Theuer",{"slug":18586,"name":18587},"thieme","Sandra Thieme",{"slug":18589,"name":18590},"thies-clasen","Marudor",{"slug":18592,"name":18593},"toernstroem","Olle Törnström",{"slug":6616,"name":18595},"Max Ullinger",{"slug":18597,"name":18598},"ulrich","Stephan Ulrich",{"slug":18600,"name":18601},"wagner","Stefan Wagner",{"slug":18603,"name":18604},"weigel","Andreas Weigel",{"slug":18606,"name":18607},"werner","Fabian Werner",{"slug":18609,"name":18610},"wolke","Sören Wolke",["Reactive",18612],{"$scookieConsent":18613,"$ssite-config":18615},{"functional":18614,"analytics":18614},false,{"_priority":18616,"env":18620,"name":18621,"url":18622},{"name":18617,"env":18618,"url":18619},-10,-15,0,"production","nuxt-app","https://synyx.de",["Set"],["ShallowReactive",18625],{"category-tutorial":-1,"authors":-1},"/blog/kategorien/tutorial"]