\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",[460,2262,2263,2267,2272,2277,2282,2287,2292,2297,2302,2307,2312,2317,2322],{"__ignoreMap":110},[657,2264,2265],{"class":659,"line":660},[657,2266,663],{"emptyLinePlaceholder":127},[657,2268,2269],{"class":659,"line":111},[657,2270,2271],{},"\u003Cpermission\n",[657,2273,2274],{"class":659,"line":671},[657,2275,2276],{}," android:name=\"com.synyx.cloudmessagetest.permission.C2D_MESSAGE\"\n",[657,2278,2279],{"class":659,"line":677},[657,2280,2281],{}," android:protectionLevel=\"signature\"/>\n",[657,2283,2284],{"class":659,"line":683},[657,2285,2286],{},"\u003Cuses-permission android:name=\"com.synyx.cloudmessagetest.permission.C2D_MESSAGE\"/>\n",[657,2288,2289],{"class":659,"line":732},[657,2290,2291],{}," \u003C!-- App receives GCM messages. -->\n",[657,2293,2294],{"class":659,"line":738},[657,2295,2296],{},"\u003Cuses-permission android:name=\"com.google.android.c2dm.permission.RECEIVE\"/>\n",[657,2298,2299],{"class":659,"line":744},[657,2300,2301],{}," \u003C!-- GCM connects to Google Services. -->\n",[657,2303,2304],{"class":659,"line":750},[657,2305,2306],{},"\u003Cuses-permission android:name=\"android.permission.INTERNET\"/>\n",[657,2308,2309],{"class":659,"line":756},[657,2310,2311],{}," \u003C!-- GCM requires a Google account. -->\n",[657,2313,2314],{"class":659,"line":907},[657,2315,2316],{},"\u003Cuses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>\n",[657,2318,2319],{"class":659,"line":913},[657,2320,2321],{}," \u003C!-- Keeps the processor from sleeping when a message is received. -->\n",[657,2323,2324],{"class":659,"line":919},[657,2325,2326],{},"\u003Cuses-permission android:name=\"android.permission.WAKE_LOCK\"/>\n",[19,2328,2329],{},"And declare a GCM broadcast receiver within the application tag:",[649,2331,2333],{"className":651,"code":2332,"language":653,"meta":110,"style":110},"\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",[460,2334,2335,2339,2344,2349,2354,2359,2364,2369,2374,2379,2384],{"__ignoreMap":110},[657,2336,2337],{"class":659,"line":660},[657,2338,663],{"emptyLinePlaceholder":127},[657,2340,2341],{"class":659,"line":111},[657,2342,2343],{},"\u003Creceiver\n",[657,2345,2346],{"class":659,"line":671},[657,2347,2348],{}," android:name=\"com.google.android.gcm.GCMBroadcastReceiver\"\n",[657,2350,2351],{"class":659,"line":677},[657,2352,2353],{}," android:permission=\"com.google.android.c2dm.permission.SEND\">\n",[657,2355,2356],{"class":659,"line":683},[657,2357,2358],{}," \u003Cintent-filter>\n",[657,2360,2361],{"class":659,"line":732},[657,2362,2363],{}," \u003Caction android:name=\"com.google.android.c2dm.intent.RECEIVE\"/>\n",[657,2365,2366],{"class":659,"line":738},[657,2367,2368],{}," \u003Caction android:name=\"com.google.android.c2dm.intent.REGISTRATION\"/>\n",[657,2370,2371],{"class":659,"line":744},[657,2372,2373],{}," \u003Ccategory android:name=\"com.synyx.cloudmessagetest\"/>\n",[657,2375,2376],{"class":659,"line":750},[657,2377,2378],{}," \u003C/intent-filter>\n",[657,2380,2381],{"class":659,"line":756},[657,2382,2383],{},"\u003C/receiver>\n",[657,2385,2386],{"class":659,"line":907},[657,2387,2388],{},"\u003Cservice android:name=\".GCMIntentService\"/>\n",[19,2390,2391],{},"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:",[649,2393,2395],{"className":2130,"code":2394,"language":2132,"meta":110,"style":110},"public class GCMIntentService extends GCMBaseIntentService {\n",[460,2396,2397],{"__ignoreMap":110},[657,2398,2399],{"class":659,"line":660},[657,2400,2394],{},[19,2402,2403],{},"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.",[19,2405,2406],{},"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.",[19,2408,2409],{},"First in the onMessage() method, we need to get access to the Main Thread of our App.",[649,2411,2413],{"className":2130,"code":2412,"language":2132,"meta":110,"style":110},"Handler h = new Handler(Looper.getMainLooper());\nh.post(new Runnable() {\n public void run() {\n }\n}\n",[460,2414,2415,2420,2425,2430,2434],{"__ignoreMap":110},[657,2416,2417],{"class":659,"line":660},[657,2418,2419],{},"Handler h = new Handler(Looper.getMainLooper());\n",[657,2421,2422],{"class":659,"line":111},[657,2423,2424],{},"h.post(new Runnable() {\n",[657,2426,2427],{"class":659,"line":671},[657,2428,2429],{}," public void run() {\n",[657,2431,2432],{"class":659,"line":677},[657,2433,2188],{},[657,2435,2436],{"class":659,"line":683},[657,2437,2438],{},"}\n",[19,2440,2441],{},"In the run() method, we get us an Intent from our MainActivity",[649,2443,2445],{"className":2130,"code":2444,"language":2132,"meta":110,"style":110},"Intent notificationIntent = new Intent(context, CloudMessageTestActivity.class);\n",[460,2446,2447],{"__ignoreMap":110},[657,2448,2449],{"class":659,"line":660},[657,2450,2444],{},[19,2452,2453],{},"And then wrap it with a PendingIntent for the NotificationBuilder",[649,2455,2457],{"className":2130,"code":2456,"language":2132,"meta":110,"style":110},"PendingIntent pendingIntent = PendingIntent.getActivity(context, 0,\n notificationIntent, 0);\n",[460,2458,2459,2464],{"__ignoreMap":110},[657,2460,2461],{"class":659,"line":660},[657,2462,2463],{},"PendingIntent pendingIntent = PendingIntent.getActivity(context, 0,\n",[657,2465,2466],{"class":659,"line":111},[657,2467,2468],{}," notificationIntent, 0);\n",[19,2470,2471],{},"Finally, use the NotificationBuilder to create and send the notification",[649,2473,2475],{"className":2130,"code":2474,"language":2132,"meta":110,"style":110},"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",[460,2476,2477,2482,2487,2492,2497,2502,2507,2512,2517,2522,2527,2532,2537],{"__ignoreMap":110},[657,2478,2479],{"class":659,"line":660},[657,2480,2481],{},"NotificationCompat.Builder builder = new NotificationCompat.Builder(\n",[657,2483,2484],{"class":659,"line":111},[657,2485,2486],{}," context);\n",[657,2488,2489],{"class":659,"line":671},[657,2490,2491],{},"builder.setContentIntent(pendingIntent);\n",[657,2493,2494],{"class":659,"line":677},[657,2495,2496],{},"builder.setAutoCancel(true);\n",[657,2498,2499],{"class":659,"line":683},[657,2500,2501],{},"builder.setSmallIcon(R.drawable.ic_launcher);\n",[657,2503,2504],{"class":659,"line":732},[657,2505,2506],{},"//this is added on the server side\n",[657,2508,2509],{"class":659,"line":738},[657,2510,2511],{},"String text = intent.getStringExtra(\"text\");\n",[657,2513,2514],{"class":659,"line":744},[657,2515,2516],{},"builder.setContentText(text);\n",[657,2518,2519],{"class":659,"line":750},[657,2520,2521],{},"builder.setContentTitle(\"New message from the cloud!\");\n",[657,2523,2524],{"class":659,"line":756},[657,2525,2526],{},"Notification noti = builder.build();\n",[657,2528,2529],{"class":659,"line":907},[657,2530,2531],{},"NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);\n",[657,2533,2534],{"class":659,"line":913},[657,2535,2536],{},"//just set the mId to 1, because we don't care about it in this case\n",[657,2538,2539],{"class":659,"line":919},[657,2540,2541],{},"mNotificationManager.notify(1, noti);\n",[19,2543,2544],{},"That’ it with the GCMIntentService.",[19,2546,2547],{},"Now we let the app register itself with GCM in our MainActivity, which is fairly easy:",[649,2549,2551],{"className":2130,"code":2550,"language":2132,"meta":110,"style":110},"//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",[460,2552,2553,2558,2563,2567,2572,2577,2582,2587,2592,2597,2602,2607,2612,2617,2622,2627,2632,2636,2641],{"__ignoreMap":110},[657,2554,2555],{"class":659,"line":660},[657,2556,2557],{},"//set your senderId from the API here!\n",[657,2559,2560],{"class":659,"line":111},[657,2561,2562],{}," private static final String SENDER_ID = \"1234567890\";\n",[657,2564,2565],{"class":659,"line":671},[657,2566,2143],{},[657,2568,2569],{"class":659,"line":677},[657,2570,2571],{},"protected void onCreate(Bundle savedInstanceState) {\n",[657,2573,2574],{"class":659,"line":683},[657,2575,2576],{}," GCMRegistrar.checkDevice(this);\n",[657,2578,2579],{"class":659,"line":732},[657,2580,2581],{}," GCMRegistrar.checkManifest(this);\n",[657,2583,2584],{"class":659,"line":738},[657,2585,2586],{}," final String regId = GCMRegistrar.getRegistrationId(this);\n",[657,2588,2589],{"class":659,"line":744},[657,2590,2591],{}," // if we don't have a regId yet, register at gcm\n",[657,2593,2594],{"class":659,"line":750},[657,2595,2596],{}," if (regId.equals(\"\")) {\n",[657,2598,2599],{"class":659,"line":756},[657,2600,2601],{}," GCMRegistrar.register(this, SENDER_ID);\n",[657,2603,2604],{"class":659,"line":907},[657,2605,2606],{}," Toast.makeText(getApplicationContext(), \"Registered GCM!\", Toast.LENGTH_LONG).show();\n",[657,2608,2609],{"class":659,"line":913},[657,2610,2611],{}," // just log the registrationId for this test case.\n",[657,2613,2614],{"class":659,"line":919},[657,2615,2616],{}," Log.i(this.getClass().getName(), \"id: \" + GCMRegistrar.getRegistrationId(this));\n",[657,2618,2619],{"class":659,"line":1006},[657,2620,2621],{}," } else {\n",[657,2623,2624],{"class":659,"line":1088},[657,2625,2626],{}," Log.i(this.getClass().getName(), \"Already registered\");\n",[657,2628,2629],{"class":659,"line":1094},[657,2630,2631],{}," Toast.makeText(getApplicationContext(), \"Already registered at GCM!\", Toast.LENGTH_LONG).show();\n",[657,2633,2634],{"class":659,"line":1100},[657,2635,2616],{},[657,2637,2638],{"class":659,"line":1106},[657,2639,2640],{}," }\n",[657,2642,2643],{"class":659,"line":1112},[657,2644,2438],{},[19,2646,2647],{},"Don’t forget to replace the SENDER_ID with yours!",[19,2649,2650],{},"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.",[19,2652,2653],{},"With this, we finished our small app and can begin implementing the server part.",[23,2655,2657],{"id":2656},"the-web-application","The Web Application",[19,2659,2660],{},"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.",[19,2662,2663,2664,700],{},"Instead of just copying the GCM server library into the project, I searched a bit and found someone, who created a\nrepository for\nit. (",[152,2665,2666],{"href":2666,"rel":2667},"https://github.com/slorber/gcm-server-repository",[156],[19,2669,2670],{},"We start with creating the Sender class, which isn’t that hard either.",[649,2672,2674],{"className":2130,"code":2673,"language":2132,"meta":110,"style":110},"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",[460,2675,2676,2681,2686,2691,2696,2701,2706,2711,2716,2721,2726,2731,2736,2741,2746,2751,2756,2761,2766,2771,2776,2781,2785,2789,2794,2798],{"__ignoreMap":110},[657,2677,2678],{"class":659,"line":660},[657,2679,2680],{},"public class GCMSender {\n",[657,2682,2683],{"class":659,"line":111},[657,2684,2685],{}," public String apiKey = null;\n",[657,2687,2688],{"class":659,"line":671},[657,2689,2690],{}," public GCMSender(String apiKey) {\n",[657,2692,2693],{"class":659,"line":677},[657,2694,2695],{}," this.apiKey = apiKey;\n",[657,2697,2698],{"class":659,"line":683},[657,2699,2700],{}," }\n",[657,2702,2703],{"class":659,"line":732},[657,2704,2705],{}," public String send(String text, String id) throws IOException {\n",[657,2707,2708],{"class":659,"line":738},[657,2709,2710],{}," Sender sender = new Sender(apiKey);\n",[657,2712,2713],{"class":659,"line":744},[657,2714,2715],{}," Builder builder = new Message.Builder();\n",[657,2717,2718],{"class":659,"line":750},[657,2719,2720],{}," builder.addData(\"text\", text);\n",[657,2722,2723],{"class":659,"line":756},[657,2724,2725],{}," Result result = sender.send(builder.build(), id, 5);\n",[657,2727,2728],{"class":659,"line":907},[657,2729,2730],{}," if (result.getMessageId() != null) {\n",[657,2732,2733],{"class":659,"line":913},[657,2734,2735],{}," String canonicalRegId = result.getCanonicalRegistrationId();\n",[657,2737,2738],{"class":659,"line":919},[657,2739,2740],{}," if (canonicalRegId != null) {\n",[657,2742,2743],{"class":659,"line":1006},[657,2744,2745],{}," // same device has more than on registration ID: update database\n",[657,2747,2748],{"class":659,"line":1088},[657,2749,2750],{}," return \"same device has more than on registration ID: update database\";\n",[657,2752,2753],{"class":659,"line":1094},[657,2754,2755],{}," }\n",[657,2757,2758],{"class":659,"line":1100},[657,2759,2760],{}," } else {\n",[657,2762,2763],{"class":659,"line":1106},[657,2764,2765],{}," String error = result.getErrorCodeName();\n",[657,2767,2768],{"class":659,"line":1112},[657,2769,2770],{}," if (error.equals(Constants.ERROR_NOT_REGISTERED)) {\n",[657,2772,2773],{"class":659,"line":1118},[657,2774,2775],{}," // application has been removed from device - unregister database\n",[657,2777,2778],{"class":659,"line":1124},[657,2779,2780],{}," return \"application has been removed from device - unregister database\";\n",[657,2782,2783],{"class":659,"line":1130},[657,2784,2755],{},[657,2786,2787],{"class":659,"line":1136},[657,2788,2640],{},[657,2790,2791],{"class":659,"line":1142},[657,2792,2793],{}," return null;\n",[657,2795,2796],{"class":659,"line":1148},[657,2797,2700],{},[657,2799,2800],{"class":659,"line":1154},[657,2801,2438],{},[19,2803,2804],{},"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:",[649,2806,2808],{"className":787,"code":2807,"language":789,"meta":110,"style":110},"\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",[460,2809,2810,2822,2827,2842,2850,2859,2888,2895,2905,2915,2925,2935,2940,2954,2963,2972,2984,3001,3024,3033,3048,3063,3068,3082,3091,3099,3116,3137,3174,3192,3222,3237,3245,3253],{"__ignoreMap":110},[657,2811,2812,2814,2817,2819],{"class":659,"line":660},[657,2813,797],{"class":800},[657,2815,2816],{"class":796},"%@ taglib prefix=\"c\" uri=\"http://java.sun.com/jsp/jstl/core\" %> ",[657,2818,797],{"class":800},[657,2820,2821],{"class":796},"%@page\n",[657,2823,2824],{"class":659,"line":111},[657,2825,2826],{"class":796},"contentType=\"text/html\" pageEncoding=\"UTF-8\"%>\n",[657,2828,2829,2832,2836,2840],{"class":659,"line":671},[657,2830,2831],{"class":796},"\u003C!",[657,2833,2835],{"class":2834},"s9eBZ","DOCTYPE",[657,2837,2839],{"class":2838},"sScJk"," html",[657,2841,804],{"class":796},[657,2843,2844,2846,2848],{"class":659,"line":677},[657,2845,797],{"class":796},[657,2847,789],{"class":2834},[657,2849,804],{"class":796},[657,2851,2852,2854,2857],{"class":659,"line":683},[657,2853,809],{"class":796},[657,2855,2856],{"class":2834},"head",[657,2858,804],{"class":796},[657,2860,2861,2864,2867,2870,2873,2877,2880,2882,2885],{"class":659,"line":732},[657,2862,2863],{"class":796}," \u003C",[657,2865,2866],{"class":2834},"meta",[657,2868,2869],{"class":2838}," http-equiv",[657,2871,2872],{"class":796},"=",[657,2874,2876],{"class":2875},"sZZnC","\"Content-Type\"",[657,2878,2879],{"class":2838}," content",[657,2881,2872],{"class":796},[657,2883,2884],{"class":2875},"\"text/html; charset=UTF-8\"",[657,2886,2887],{"class":796}," />\n",[657,2889,2890,2892],{"class":659,"line":738},[657,2891,2863],{"class":796},[657,2893,2894],{"class":2834},"link\n",[657,2896,2897,2900,2902],{"class":659,"line":744},[657,2898,2899],{"class":2838}," rel",[657,2901,2872],{"class":796},[657,2903,2904],{"class":2875},"\"stylesheet\"\n",[657,2906,2907,2910,2912],{"class":659,"line":750},[657,2908,2909],{"class":2838}," type",[657,2911,2872],{"class":796},[657,2913,2914],{"class":2875},"\"text/css\"\n",[657,2916,2917,2920,2922],{"class":659,"line":756},[657,2918,2919],{"class":2838}," href",[657,2921,2872],{"class":796},[657,2923,2924],{"class":2875},"\"/GCMTestServer/frontend_resources/style.css\"\n",[657,2926,2927,2930,2932],{"class":659,"line":907},[657,2928,2929],{"class":2838}," media",[657,2931,2872],{"class":796},[657,2933,2934],{"class":2875},"\"screen\"\n",[657,2936,2937],{"class":659,"line":913},[657,2938,2939],{"class":796}," />\n",[657,2941,2942,2944,2947,2950,2952],{"class":659,"line":919},[657,2943,2863],{"class":796},[657,2945,2946],{"class":2834},"title",[657,2948,2949],{"class":796},">GCM Sender\u003C/",[657,2951,2946],{"class":2834},[657,2953,804],{"class":796},[657,2955,2956,2959,2961],{"class":659,"line":1006},[657,2957,2958],{"class":796}," \u003C/",[657,2960,2856],{"class":2834},[657,2962,804],{"class":796},[657,2964,2965,2967,2970],{"class":659,"line":1088},[657,2966,809],{"class":796},[657,2968,2969],{"class":2834},"body",[657,2971,804],{"class":796},[657,2973,2974,2976,2978,2980,2982],{"class":659,"line":1094},[657,2975,2863],{"class":796},[657,2977,15],{"class":2834},[657,2979,2949],{"class":796},[657,2981,15],{"class":2834},[657,2983,804],{"class":796},[657,2985,2986,2988,2991,2994,2996,2999],{"class":659,"line":1100},[657,2987,2863],{"class":796},[657,2989,2990],{"class":800},"c:if",[657,2992,2993],{"class":2838}," test",[657,2995,2872],{"class":796},[657,2997,2998],{"class":2875},"\"${success == true}\"",[657,3000,804],{"class":796},[657,3002,3003,3006,3009,3012,3014,3017,3020,3022],{"class":659,"line":1106},[657,3004,3005],{"class":796}," \u003C",[657,3007,3008],{"class":2834},"div",[657,3010,3011],{"class":2838}," class",[657,3013,2872],{"class":796},[657,3015,3016],{"class":2875},"\"success\"",[657,3018,3019],{"class":796},">sending successful!\u003C/",[657,3021,3008],{"class":2834},[657,3023,804],{"class":796},[657,3025,3026,3029,3031],{"class":659,"line":1112},[657,3027,3028],{"class":796}," \u003C/",[657,3030,2990],{"class":800},[657,3032,804],{"class":796},[657,3034,3035,3037,3039,3041,3043,3046],{"class":659,"line":1118},[657,3036,2863],{"class":796},[657,3038,2990],{"class":800},[657,3040,2993],{"class":2838},[657,3042,2872],{"class":796},[657,3044,3045],{"class":2875},"\"${error == true}\"",[657,3047,804],{"class":796},[657,3049,3050,3052,3054,3056,3058,3061],{"class":659,"line":1124},[657,3051,3005],{"class":796},[657,3053,3008],{"class":2834},[657,3055,3011],{"class":2838},[657,3057,2872],{"class":796},[657,3059,3060],{"class":2875},"\"error\"",[657,3062,804],{"class":796},[657,3064,3065],{"class":659,"line":1130},[657,3066,3067],{"class":796}," Error at sending!\n",[657,3069,3070,3073,3075,3078,3080],{"class":659,"line":1136},[657,3071,3072],{"class":796}," \u003C",[657,3074,19],{"class":2834},[657,3076,3077],{"class":796},">${errormessage}\u003C/",[657,3079,19],{"class":2834},[657,3081,804],{"class":796},[657,3083,3084,3087,3089],{"class":659,"line":1142},[657,3085,3086],{"class":796}," \u003C/",[657,3088,3008],{"class":2834},[657,3090,804],{"class":796},[657,3092,3093,3095,3097],{"class":659,"line":1148},[657,3094,3028],{"class":796},[657,3096,2990],{"class":800},[657,3098,804],{"class":796},[657,3100,3101,3103,3106,3109,3111,3114],{"class":659,"line":1154},[657,3102,2863],{"class":796},[657,3104,3105],{"class":2834},"form",[657,3107,3108],{"class":2838}," method",[657,3110,2872],{"class":796},[657,3112,3113],{"class":2875},"\"POST\"",[657,3115,804],{"class":796},[657,3117,3118,3120,3123,3126,3128,3131,3134],{"class":659,"line":1160},[657,3119,3005],{"class":796},[657,3121,3122],{"class":2834},"label",[657,3124,3125],{"class":2838}," for",[657,3127,2872],{"class":796},[657,3129,3130],{"class":2875},"\"text\"",[657,3132,3133],{"class":796},">Text\u003C/",[657,3135,3136],{"class":2834},"label\n",[657,3138,3139,3142,3145,3148,3150,3152,3155,3157,3159,3162,3164,3166,3169,3172],{"class":659,"line":1166},[657,3140,3141],{"class":796}," >\u003C",[657,3143,3144],{"class":2834},"input",[657,3146,3147],{"class":2838}," name",[657,3149,2872],{"class":796},[657,3151,3130],{"class":2875},[657,3153,3154],{"class":2838}," id",[657,3156,2872],{"class":796},[657,3158,3130],{"class":2875},[657,3160,3161],{"class":2838}," type",[657,3163,2872],{"class":796},[657,3165,3130],{"class":2875},[657,3167,3168],{"class":796}," />\u003C",[657,3170,3171],{"class":2834},"br",[657,3173,2887],{"class":796},[657,3175,3176,3178,3180,3182,3184,3187,3190],{"class":659,"line":1172},[657,3177,3005],{"class":796},[657,3179,3122],{"class":2834},[657,3181,3125],{"class":2838},[657,3183,2872],{"class":796},[657,3185,3186],{"class":2875},"\"id\"",[657,3188,3189],{"class":796},">Registration-Id\u003C/",[657,3191,3136],{"class":2834},[657,3193,3194,3196,3198,3200,3202,3204,3206,3208,3210,3212,3214,3216,3218,3220],{"class":659,"line":1178},[657,3195,3141],{"class":796},[657,3197,3144],{"class":2834},[657,3199,3147],{"class":2838},[657,3201,2872],{"class":796},[657,3203,3186],{"class":2875},[657,3205,3154],{"class":2838},[657,3207,2872],{"class":796},[657,3209,3186],{"class":2875},[657,3211,3161],{"class":2838},[657,3213,2872],{"class":796},[657,3215,3130],{"class":2875},[657,3217,3168],{"class":796},[657,3219,3171],{"class":2834},[657,3221,2887],{"class":796},[657,3223,3224,3226,3228,3230,3232,3235],{"class":659,"line":1183},[657,3225,3005],{"class":796},[657,3227,3144],{"class":2834},[657,3229,3161],{"class":2838},[657,3231,2872],{"class":796},[657,3233,3234],{"class":2875},"\"submit\"",[657,3236,2887],{"class":796},[657,3238,3239,3241,3243],{"class":659,"line":1188},[657,3240,3028],{"class":796},[657,3242,3105],{"class":2834},[657,3244,804],{"class":796},[657,3246,3247,3249,3251],{"class":659,"line":1194},[657,3248,2958],{"class":796},[657,3250,2969],{"class":2834},[657,3252,804],{"class":796},[657,3254,3255,3257,3259],{"class":659,"line":1199},[657,3256,843],{"class":796},[657,3258,789],{"class":2834},[657,3260,804],{"class":796},[19,3262,3263],{},"In the corresponding Controller to process the request, send the message:",[649,3265,3267],{"className":2130,"code":3266,"language":2132,"meta":110,"style":110}," @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",[460,3268,3269,3274,3279,3284,3289,3294,3299,3304,3309,3314,3319,3323,3328,3333,3338,3342,3347,3351,3356],{"__ignoreMap":110},[657,3270,3271],{"class":659,"line":660},[657,3272,3273],{}," @RequestMapping(value = \"send\", method = RequestMethod.POST)\n",[657,3275,3276],{"class":659,"line":111},[657,3277,3278],{}," public String send(Model model,\n",[657,3280,3281],{"class":659,"line":671},[657,3282,3283],{}," @RequestParam(\"text\") String text,\n",[657,3285,3286],{"class":659,"line":677},[657,3287,3288],{}," @RequestParam(\"id\") String id) {\n",[657,3290,3291],{"class":659,"line":683},[657,3292,3293],{}," String error = null;\n",[657,3295,3296],{"class":659,"line":732},[657,3297,3298],{}," try {\n",[657,3300,3301],{"class":659,"line":738},[657,3302,3303],{}," error = gcmSender.send(text, id);\n",[657,3305,3306],{"class":659,"line":744},[657,3307,3308],{}," } catch (IOException ex) {\n",[657,3310,3311],{"class":659,"line":750},[657,3312,3313],{}," model.addAttribute(\"error\", true);\n",[657,3315,3316],{"class":659,"line":756},[657,3317,3318],{}," model.addAttribute(\"errormessage\", ex.getMessage());\n",[657,3320,3321],{"class":659,"line":907},[657,3322,2178],{},[657,3324,3325],{"class":659,"line":913},[657,3326,3327],{}," if (error == null) {\n",[657,3329,3330],{"class":659,"line":919},[657,3331,3332],{}," model.addAttribute(\"success\", true);\n",[657,3334,3335],{"class":659,"line":1006},[657,3336,3337],{}," } else {\n",[657,3339,3340],{"class":659,"line":1088},[657,3341,3313],{},[657,3343,3344],{"class":659,"line":1094},[657,3345,3346],{}," model.addAttribute(\"errormessage\", error);\n",[657,3348,3349],{"class":659,"line":1100},[657,3350,2178],{},[657,3352,3353],{"class":659,"line":1106},[657,3354,3355],{}," return \"sender\";\n",[657,3357,3358],{"class":659,"line":1112},[657,3359,2188],{},[19,3361,3362],{},"Now we are ready to test it!",[19,3364,3365],{},"Start the app, copy the RegistrationId from the Logs, start the Server, enter the RegistrationId and a small text, and\nthere you go:",[19,3367,3368],{},[33,3369],{"alt":3370,"src":3371},"\"notification\"","https://media.synyx.de/uploads//2012/12/notification-300x221.jpg",[19,3373,3374],{},"To get a better understanding, you can also read the rest of the starting guide and the other documentation.",[19,3376,3377],{},"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 😛",[19,3379,3380,3381],{},"As promised, here are the full sources:",[152,3382,3385],{"href":3383,"rel":3384},"https://media.synyx.de/uploads//2012/12/CloudMessageTest.zip",[156],"CloudMessageTest",[1341,3387,3388],{},"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":110,"searchDepth":111,"depth":111,"links":3390},[3391,3392],{"id":2250,"depth":111,"text":2251},{"id":2656,"depth":111,"text":2657},[120,483],"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":2225,"description":2234},"blog/a-small-look-into-google-cloud-messages",[133,3401,3402,3403,3404,3405,3406],"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":3410,"title":3411,"author":3412,"body":3413,"category":4063,"date":4064,"description":4065,"extension":124,"link":4066,"meta":4067,"navigation":127,"path":4068,"seo":4069,"slug":3417,"stem":4070,"tags":4071,"teaser":4079,"__hash__":4080},"blog/blog/android-expandable-listview.md","Android: Expandable ListView",[617],{"type":12,"value":3414,"toc":4061},[3415,3418,3421,3424,3612,3615,3680,3683,3744,3747,3750,3759,3762,3765,3802,3805,3820,3823,3871,3874,3945,3948,3996,3999,4024,4027,4030,4041,4051,4059],[15,3416,3411],{"id":3417},"android-expandable-listview",[19,3419,3420],{},"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.",[19,3422,3423],{},"To achieve this goal, we first need to implement a basic Adapter that provides our ListView with the entries:",[649,3425,3427],{"className":2130,"code":3426,"language":2132,"meta":110,"style":110},"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",[460,3428,3429,3434,3439,3444,3449,3454,3458,3463,3468,3473,3477,3481,3486,3491,3496,3501,3505,3509,3514,3519,3523,3527,3532,3537,3542,3547,3552,3556,3561,3565,3570,3575,3580,3585,3589,3594,3599,3604,3608],{"__ignoreMap":110},[657,3430,3431],{"class":659,"line":660},[657,3432,3433],{},"private class ExpandableListAdapter extends BaseAdapter {\n",[657,3435,3436],{"class":659,"line":111},[657,3437,3438],{}," private List entries;\n",[657,3440,3441],{"class":659,"line":671},[657,3442,3443],{}," private Context context;\n",[657,3445,3446],{"class":659,"line":677},[657,3447,3448],{}," public ExpandableListAdapter(List entries) {\n",[657,3450,3451],{"class":659,"line":683},[657,3452,3453],{}," this.entries = entries;\n",[657,3455,3456],{"class":659,"line":732},[657,3457,2188],{},[657,3459,3460],{"class":659,"line":738},[657,3461,3462],{}," @Override\n",[657,3464,3465],{"class":659,"line":744},[657,3466,3467],{}," public int getCount() {\n",[657,3469,3470],{"class":659,"line":750},[657,3471,3472],{}," return entries.size();\n",[657,3474,3475],{"class":659,"line":756},[657,3476,2188],{},[657,3478,3479],{"class":659,"line":907},[657,3480,3462],{},[657,3482,3483],{"class":659,"line":913},[657,3484,3485],{}," public Object getItem(int position) {\n",[657,3487,3488],{"class":659,"line":919},[657,3489,3490],{}," if (position >= 0 && position \u003C entries.size())\n",[657,3492,3493],{"class":659,"line":1006},[657,3494,3495],{}," return this.entries.get(position);\n",[657,3497,3498],{"class":659,"line":1088},[657,3499,3500],{}," return null;\n",[657,3502,3503],{"class":659,"line":1094},[657,3504,2188],{},[657,3506,3507],{"class":659,"line":1100},[657,3508,3462],{},[657,3510,3511],{"class":659,"line":1106},[657,3512,3513],{}," public long getItemId(int position) {\n",[657,3515,3516],{"class":659,"line":1112},[657,3517,3518],{}," return position;\n",[657,3520,3521],{"class":659,"line":1118},[657,3522,2188],{},[657,3524,3525],{"class":659,"line":1124},[657,3526,3462],{},[657,3528,3529],{"class":659,"line":1130},[657,3530,3531],{}," public View getView(int position, View convertView, ViewGroup parent) {\n",[657,3533,3534],{"class":659,"line":1136},[657,3535,3536],{}," View view = null;\n",[657,3538,3539],{"class":659,"line":1142},[657,3540,3541],{}," //reuse the convertView\n",[657,3543,3544],{"class":659,"line":1148},[657,3545,3546],{}," if (convertView == null) {\n",[657,3548,3549],{"class":659,"line":1154},[657,3550,3551],{}," view = getLayoutInflater().inflate(R.layout.row, null);\n",[657,3553,3554],{"class":659,"line":1160},[657,3555,3337],{},[657,3557,3558],{"class":659,"line":1166},[657,3559,3560],{}," view = convertView;\n",[657,3562,3563],{"class":659,"line":1172},[657,3564,2178],{},[657,3566,3567],{"class":659,"line":1178},[657,3568,3569],{}," String entry = entries.get(position);\n",[657,3571,3572],{"class":659,"line":1183},[657,3573,3574],{}," view.setTag(position);\n",[657,3576,3577],{"class":659,"line":1188},[657,3578,3579],{}," fillView(view, entry);\n",[657,3581,3582],{"class":659,"line":1194},[657,3583,3584],{}," return view;\n",[657,3586,3587],{"class":659,"line":1199},[657,3588,2188],{},[657,3590,3591],{"class":659,"line":1205},[657,3592,3593],{}," private void fillView(View view, String entry) {\n",[657,3595,3596],{"class":659,"line":1210},[657,3597,3598],{}," TextView text = (TextView) view.findViewById(R.id.text);\n",[657,3600,3601],{"class":659,"line":1215},[657,3602,3603],{}," text.setText(entry);\n",[657,3605,3606],{"class":659,"line":1220},[657,3607,2188],{},[657,3609,3610],{"class":659,"line":1225},[657,3611,2438],{},[19,3613,3614],{},"For this example, I used a simple view for the rows, with just a TextView in it:",[649,3616,3618],{"className":651,"code":3617,"language":653,"meta":110,"style":110},"\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",[460,3619,3620,3625,3630,3635,3640,3645,3650,3655,3660,3665,3670,3675],{"__ignoreMap":110},[657,3621,3622],{"class":659,"line":660},[657,3623,3624],{},"\u003C?xml version=\"1.0\" encoding=\"utf-8\"?>\n",[657,3626,3627],{"class":659,"line":111},[657,3628,3629],{},"\u003CLinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n",[657,3631,3632],{"class":659,"line":671},[657,3633,3634],{}," android:layout_width=\"match_parent\"\n",[657,3636,3637],{"class":659,"line":677},[657,3638,3639],{}," android:layout_height=\"match_parent\"\n",[657,3641,3642],{"class":659,"line":683},[657,3643,3644],{}," android:orientation=\"vertical\">\n",[657,3646,3647],{"class":659,"line":732},[657,3648,3649],{}," \u003CTextView\n",[657,3651,3652],{"class":659,"line":738},[657,3653,3654],{}," android:id=\"@+id/text\"\n",[657,3656,3657],{"class":659,"line":744},[657,3658,3659],{}," android:layout_width=\"match_parent\"\n",[657,3661,3662],{"class":659,"line":750},[657,3663,3664],{}," android:layout_height=\"30dp\"\n",[657,3666,3667],{"class":659,"line":756},[657,3668,3669],{}," android:gravity=\"center_vertical\"\n",[657,3671,3672],{"class":659,"line":907},[657,3673,3674],{}," android:padding=\"5dp\"/>\n",[657,3676,3677],{"class":659,"line":913},[657,3678,3679],{},"\u003C/LinearLayout>\n",[19,3681,3682],{},"Then we add the created adapter to a ListView (or a ListActivity like here):",[649,3684,3686],{"className":2130,"code":3685,"language":2132,"meta":110,"style":110},"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",[460,3687,3688,3693,3697,3702,3707,3712,3717,3722,3726,3731,3735,3740],{"__ignoreMap":110},[657,3689,3690],{"class":659,"line":660},[657,3691,3692],{},"public class ExpandableListActivity extends ListActivity {\n",[657,3694,3695],{"class":659,"line":111},[657,3696,2143],{},[657,3698,3699],{"class":659,"line":671},[657,3700,3701],{},"public void onCreate(Bundle savedInstanceState) {\n",[657,3703,3704],{"class":659,"line":677},[657,3705,3706],{}," super.onCreate(savedInstanceState);\n",[657,3708,3709],{"class":659,"line":683},[657,3710,3711],{}," List\u003CString> entries = new ArrayList\u003CString>();\n",[657,3713,3714],{"class":659,"line":732},[657,3715,3716],{}," for (int i = 1; i \u003C= 101; i++) {\n",[657,3718,3719],{"class":659,"line":738},[657,3720,3721],{}," entries.add(\"Entry \" + i);\n",[657,3723,3724],{"class":659,"line":744},[657,3725,2640],{},[657,3727,3728],{"class":659,"line":750},[657,3729,3730],{}," setListAdapter(new ExpandableListAdapter(entries, this));\n",[657,3732,3733],{"class":659,"line":756},[657,3734,2438],{},[657,3736,3737],{"class":659,"line":907},[657,3738,3739],{}," private class ExpandableListAdapter extends BaseAdapter{....}\n",[657,3741,3742],{"class":659,"line":913},[657,3743,2438],{},[19,3745,3746],{},"The list as it is now shows all entries, so the next thing to do, is implementing the limiting function to the adapter.",[19,3748,3749],{},"Let’s start by giving our adapter a member variable for the limit:",[649,3751,3753],{"className":2130,"code":3752,"language":2132,"meta":110,"style":110},"private int limit = 20;\n",[460,3754,3755],{"__ignoreMap":110},[657,3756,3757],{"class":659,"line":660},[657,3758,3752],{},[19,3760,3761],{},"Next we need to modify some of the methods to get this working:",[19,3763,3764],{},"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.",[649,3766,3768],{"className":2130,"code":3767,"language":2132,"meta":110,"style":110},"@Override\npublic int getCount() {\n if (entries.size() \u003C= limit) {\n return entries.size();\n }\n return limit;\n}\n",[460,3769,3770,3774,3779,3784,3789,3793,3798],{"__ignoreMap":110},[657,3771,3772],{"class":659,"line":660},[657,3773,2143],{},[657,3775,3776],{"class":659,"line":111},[657,3777,3778],{},"public int getCount() {\n",[657,3780,3781],{"class":659,"line":671},[657,3782,3783],{}," if (entries.size() \u003C= limit) {\n",[657,3785,3786],{"class":659,"line":677},[657,3787,3788],{}," return entries.size();\n",[657,3790,3791],{"class":659,"line":683},[657,3792,2640],{},[657,3794,3795],{"class":659,"line":732},[657,3796,3797],{}," return limit;\n",[657,3799,3800],{"class":659,"line":738},[657,3801,2438],{},[19,3803,3804],{},"Next, we need to adjust the getItem method with an further clause in the if",[649,3806,3808],{"className":2130,"code":3807,"language":2132,"meta":110,"style":110},"if (position >= 0 && position \u003C limit && position \u003C entries.size())\n return entries.get(position);\n",[460,3809,3810,3815],{"__ignoreMap":110},[657,3811,3812],{"class":659,"line":660},[657,3813,3814],{},"if (position >= 0 && position \u003C limit && position \u003C entries.size())\n",[657,3816,3817],{"class":659,"line":111},[657,3818,3819],{}," return entries.get(position);\n",[19,3821,3822],{},"Now for the biggest change for our new functionality: the implementation of the ‘more button’.",[649,3824,3826],{"className":2130,"code":3825,"language":2132,"meta":110,"style":110},"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",[460,3827,3828,3833,3838,3843,3848,3853,3857,3862,3867],{"__ignoreMap":110},[657,3829,3830],{"class":659,"line":660},[657,3831,3832],{},"private LinearLayout getMoreView() {\n",[657,3834,3835],{"class":659,"line":111},[657,3836,3837],{}," LinearLayout moreView = (LinearLayout) getLayoutInflater().inflate(R.layout.more_row, null);\n",[657,3839,3840],{"class":659,"line":671},[657,3841,3842],{}," moreView.setOnClickListener(new View.OnClickListener() {\n",[657,3844,3845],{"class":659,"line":677},[657,3846,3847],{}," public void onClick(View v) {\n",[657,3849,3850],{"class":659,"line":683},[657,3851,3852],{}," listAdapter.increaseLimit();\n",[657,3854,3855],{"class":659,"line":732},[657,3856,2178],{},[657,3858,3859],{"class":659,"line":738},[657,3860,3861],{}," });\n",[657,3863,3864],{"class":659,"line":744},[657,3865,3866],{}," return moreView;\n",[657,3868,3869],{"class":659,"line":750},[657,3870,2438],{},[19,3872,3873],{},"Create another simple xml like this (disregarded i18n for this simple test case. Of course, Strings should normally be\ndeclared in the strings.xml):",[649,3875,3877],{"className":651,"code":3876,"language":653,"meta":110,"style":110},"\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",[460,3878,3879,3883,3887,3892,3897,3902,3907,3912,3916,3921,3926,3931,3936,3941],{"__ignoreMap":110},[657,3880,3881],{"class":659,"line":660},[657,3882,3624],{},[657,3884,3885],{"class":659,"line":111},[657,3886,3629],{},[657,3888,3889],{"class":659,"line":671},[657,3890,3891],{}," android:layout_width=\"fill_parent\"\n",[657,3893,3894],{"class":659,"line":677},[657,3895,3896],{}," android:layout_height=\"wrap_content\"\n",[657,3898,3899],{"class":659,"line":683},[657,3900,3901],{}," android:orientation=\"horizontal\"\n",[657,3903,3904],{"class":659,"line":732},[657,3905,3906],{}," android:paddingLeft=\"20dp\"\n",[657,3908,3909],{"class":659,"line":738},[657,3910,3911],{}," android:paddingRight=\"20dp\">\n",[657,3913,3914],{"class":659,"line":744},[657,3915,3649],{},[657,3917,3918],{"class":659,"line":750},[657,3919,3920],{}," android:id=\"@+id/MoreRowText\"\n",[657,3922,3923],{"class":659,"line":756},[657,3924,3925],{}," android:layout_width=\"fill_parent\"\n",[657,3927,3928],{"class":659,"line":907},[657,3929,3930],{}," android:layout_height=\"40dp\"\n",[657,3932,3933],{"class":659,"line":913},[657,3934,3935],{}," android:text=\"Load more...\"\n",[657,3937,3938],{"class":659,"line":919},[657,3939,3940],{}," android:gravity=\"center\"/>\n",[657,3942,3943],{"class":659,"line":1006},[657,3944,3679],{},[19,3946,3947],{},"And add a method to the adapter to increase the limit:",[649,3949,3951],{"className":2130,"code":3950,"language":2132,"meta":110,"style":110}," 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",[460,3952,3953,3958,3963,3968,3973,3978,3982,3987,3992],{"__ignoreMap":110},[657,3954,3955],{"class":659,"line":660},[657,3956,3957],{}," public void increaseLimit() {\n",[657,3959,3960],{"class":659,"line":111},[657,3961,3962],{}," limit+=20;\n",[657,3964,3965],{"class":659,"line":671},[657,3966,3967],{}," //remove the button if we can no longer expand the list\n",[657,3969,3970],{"class":659,"line":677},[657,3971,3972],{}," if (limit >= entries.size()) {\n",[657,3974,3975],{"class":659,"line":683},[657,3976,3977],{}," getListView().removeFooterView(moreView);\n",[657,3979,3980],{"class":659,"line":732},[657,3981,2640],{},[657,3983,3984],{"class":659,"line":738},[657,3985,3986],{}," //notify to redraw the list\n",[657,3988,3989],{"class":659,"line":744},[657,3990,3991],{}," notifyDataSetChanged();\n",[657,3993,3994],{"class":659,"line":750},[657,3995,2640],{},[19,3997,3998],{},"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):",[649,4000,4002],{"className":2130,"code":4001,"language":2132,"meta":110,"style":110},"moreView = getMoreView();\nlistAdapter = new ExpandableListAdapter(entries);\ngetListView().addFooterView(moreView);\nsetListAdapter(listAdapter);\n",[460,4003,4004,4009,4014,4019],{"__ignoreMap":110},[657,4005,4006],{"class":659,"line":660},[657,4007,4008],{},"moreView = getMoreView();\n",[657,4010,4011],{"class":659,"line":111},[657,4012,4013],{},"listAdapter = new ExpandableListAdapter(entries);\n",[657,4015,4016],{"class":659,"line":671},[657,4017,4018],{},"getListView().addFooterView(moreView);\n",[657,4020,4021],{"class":659,"line":677},[657,4022,4023],{},"setListAdapter(listAdapter);\n",[19,4025,4026],{},"And that’s it for the simple case of a static list 🙂",[19,4028,4029],{},"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.",[519,4031,4032,4035,4038],{},[88,4033,4034],{},"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",[88,4036,4037],{},"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)",[88,4039,4040],{},"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.",[19,4042,4043,4044,4050],{},"A Little homework for you: Combine it with\nthe ",[152,4045,4049],{"href":4046,"rel":4047,"title":4048},"http://blog.synyx.de/2011/11/android-listview-with-rounded-corners/",[156],"android listview with rounded corners","rounded corners example","\nto make it look better 😛",[19,4052,4053,4054],{},"Here’s the source, btw:",[152,4055,4058],{"href":4056,"rel":4057},"https://media.synyx.de/uploads//2012/12/ExpandableListview.zip",[156],"ExpandableListview",[1341,4060,2074],{},{"title":110,"searchDepth":111,"depth":111,"links":4062},[],[120,483],"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":3411,"description":3420},"blog/android-expandable-listview",[133,4072,4073,4074,4075,4076,4077,4078],"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":4082,"title":4083,"author":4084,"body":4085,"category":4300,"date":4301,"description":4302,"extension":124,"link":4303,"meta":4304,"navigation":127,"path":4305,"seo":4306,"slug":4307,"stem":4308,"tags":4309,"teaser":4314,"__hash__":4315},"blog/blog/android-2-1-sqlite-problem-with-querybuilder-and-distinct.md","Android 2.1 SQLite: problem with QueryBuilder and Distinct",[617],{"type":12,"value":4086,"toc":4298},[4087,4090,4093,4096,4274,4281,4284,4293,4296],[15,4088,4083],{"id":4089},"android-21-sqlite-problem-with-querybuilder-and-distinct",[19,4091,4092],{},"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.",[19,4094,4095],{},"Here’s the simplified code:",[649,4097,4099],{"className":2130,"code":4098,"language":2132,"meta":110,"style":110},"//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",[460,4100,4101,4106,4111,4116,4121,4126,4131,4136,4141,4146,4151,4156,4161,4166,4171,4176,4181,4186,4191,4196,4201,4206,4211,4216,4221,4226,4231,4236,4241,4246,4251,4256,4260,4265,4270],{"__ignoreMap":110},[657,4102,4103],{"class":659,"line":660},[657,4104,4105],{},"//member, a SQLiteOpenHelper\n",[657,4107,4108],{"class":659,"line":111},[657,4109,4110],{},"BackendOpenHelper helper;\n",[657,4112,4113],{"class":659,"line":671},[657,4114,4115],{},"//...\n",[657,4117,4118],{"class":659,"line":677},[657,4119,4120],{},"public List \u003CExample> getExamples(String arg){\n",[657,4122,4123],{"class":659,"line":683},[657,4124,4125],{},"SQLiteQueryBuilder builder = new SQLiteQueryBuilder();\n",[657,4127,4128],{"class":659,"line":732},[657,4129,4130],{}," builder.setTables(\"example e JOIN\n",[657,4132,4133],{"class":659,"line":738},[657,4134,4135],{}," secondtable s ON e.id = s.example_id\");\n",[657,4137,4138],{"class":659,"line":744},[657,4139,4140],{}," Map\u003CString, String> projectionMap =\n",[657,4142,4143],{"class":659,"line":750},[657,4144,4145],{}," new HashMap\u003CString, String>();\n",[657,4147,4148],{"class":659,"line":756},[657,4149,4150],{}," projectionMap.put(\"id\", \"e.id\");\n",[657,4152,4153],{"class":659,"line":907},[657,4154,4155],{}," //... put in some more values ...\n",[657,4157,4158],{"class":659,"line":913},[657,4159,4160],{}," builder.setProjectionMap(projectionMap);\n",[657,4162,4163],{"class":659,"line":919},[657,4164,4165],{}," builder.setDistinct(true);\n",[657,4167,4168],{"class":659,"line":1006},[657,4169,4170],{}," builder.appendWhere(\" e.someRow = ? \");\n",[657,4172,4173],{"class":659,"line":1088},[657,4174,4175],{}," //... some more wheres ...\n",[657,4177,4178],{"class":659,"line":1094},[657,4179,4180],{}," SQLiteDatabase db = helper.getReadableDatabase();\n",[657,4182,4183],{"class":659,"line":1100},[657,4184,4185],{}," String[] selectionArgs = new String[] {\n",[657,4187,4188],{"class":659,"line":1106},[657,4189,4190],{}," arg\n",[657,4192,4193],{"class":659,"line":1112},[657,4194,4195],{}," };\n",[657,4197,4198],{"class":659,"line":1118},[657,4199,4200],{}," Cursor cursor = builder.query(db, null,\n",[657,4202,4203],{"class":659,"line":1124},[657,4204,4205],{}," null, selectionArgs, null, null, null);\n",[657,4207,4208],{"class":659,"line":1130},[657,4209,4210],{}," if (cursor.moveToFirst()) {\n",[657,4212,4213],{"class":659,"line":1136},[657,4214,4215],{}," while (cursor.isAfterLast() == false) {\n",[657,4217,4218],{"class":659,"line":1142},[657,4219,4220],{}," int index = cursor.getColumnIndex(\"id\");\n",[657,4222,4223],{"class":659,"line":1148},[657,4224,4225],{}," //on android 2.1, index is returned as -1\n",[657,4227,4228],{"class":659,"line":1154},[657,4229,4230],{}," //on newer versions as 1\n",[657,4232,4233],{"class":659,"line":1160},[657,4234,4235],{}," int id = cursor.getInt(index);\n",[657,4237,4238],{"class":659,"line":1166},[657,4239,4240],{}," //crashes if index is -1\n",[657,4242,4243],{"class":659,"line":1172},[657,4244,4245],{}," //...\n",[657,4247,4248],{"class":659,"line":1178},[657,4249,4250],{}," cursor.moveToNext();\n",[657,4252,4253],{"class":659,"line":1183},[657,4254,4255],{}," }\n",[657,4257,4258],{"class":659,"line":1188},[657,4259,2178],{},[657,4261,4262],{"class":659,"line":1194},[657,4263,4264],{}," cursor.close();\n",[657,4266,4267],{"class":659,"line":1199},[657,4268,4269],{}," //...\n",[657,4271,4272],{"class":659,"line":1205},[657,4273,2438],{},[19,4275,4276,4277,4280],{},"After some research I found out that this apparently happens, when using ",[693,4278,4279],{},"distinct"," with the QueryBuilder on android\n2.1.",[19,4282,4283],{},"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).",[649,4285,4287],{"className":2130,"code":4286,"language":2132,"meta":110,"style":110}," int id = cursor.getInt(1);\n",[460,4288,4289],{"__ignoreMap":110},[657,4290,4291],{"class":659,"line":660},[657,4292,4286],{},[19,4294,4295],{},"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.",[1341,4297,2074],{},{"title":110,"searchDepth":111,"depth":111,"links":4299},[],[120,483],"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":4083,"description":4092},"android-2-1-sqlite-problem-with-querybuilder-and-distinct","blog/android-2-1-sqlite-problem-with-querybuilder-and-distinct",[4310,133,4311,4312,4313],"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":4317,"title":4318,"author":4319,"body":4320,"category":4636,"date":4637,"description":4638,"extension":124,"link":4639,"meta":4640,"navigation":127,"path":4641,"seo":4642,"slug":4324,"stem":4643,"tags":4644,"teaser":4646,"__hash__":4647},"blog/blog/android-listview-with-rounded-corners.md","Android: ListView with rounded corners",[617],{"type":12,"value":4321,"toc":4634},[4322,4325,4328,4331,4334,4337,4404,4407,4493,4496,4499,4542,4545,4548,4629,4632],[15,4323,4318],{"id":4324},"android-listview-with-rounded-corners",[19,4326,4327],{},"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.",[19,4329,4330],{},"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:",[19,4332,4333],{},"First off, we need the drawables for the backgrounds of the Lists entries:",[19,4335,4336],{},"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:",[649,4338,4340],{"className":651,"code":4339,"language":653,"meta":110,"style":110},"\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",[460,4341,4342,4347,4352,4357,4362,4367,4372,4377,4382,4386,4391,4395,4399],{"__ignoreMap":110},[657,4343,4344],{"class":659,"line":660},[657,4345,4346],{},"\u003C?xml version=\"1.0\" encoding=\"UTF-8\"?>\n",[657,4348,4349],{"class":659,"line":111},[657,4350,4351],{},"\u003Clayer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n",[657,4353,4354],{"class":659,"line":671},[657,4355,4356],{}," \u003Citem>\n",[657,4358,4359],{"class":659,"line":677},[657,4360,4361],{}," \u003Cshape>\n",[657,4363,4364],{"class":659,"line":683},[657,4365,4366],{}," \u003Cstroke android:width=\"1px\" android:color=\"#ffbbbbbb\"/>\n",[657,4368,4369],{"class":659,"line":732},[657,4370,4371],{}," \u003C/shape>\n",[657,4373,4374],{"class":659,"line":738},[657,4375,4376],{}," \u003C/item>\n",[657,4378,4379],{"class":659,"line":744},[657,4380,4381],{}," \u003Citem android:bottom=\"1dp\" android:left=\"1dp\" android:right=\"1dp\">\n",[657,4383,4384],{"class":659,"line":750},[657,4385,4361],{},[657,4387,4388],{"class":659,"line":756},[657,4389,4390],{}," \u003Csolid android:color=\"#ffffffff\"/>\n",[657,4392,4393],{"class":659,"line":907},[657,4394,4371],{},[657,4396,4397],{"class":659,"line":913},[657,4398,4376],{},[657,4400,4401],{"class":659,"line":919},[657,4402,4403],{},"\u003C/layer-list>\n",[19,4405,4406],{},"For the rounded corners, create another xml, “rounded_corner_top.xml”:",[649,4408,4410],{"className":651,"code":4409,"language":653,"meta":110,"style":110},"\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",[460,4411,4412,4416,4420,4424,4428,4433,4438,4443,4448,4452,4456,4461,4465,4469,4473,4477,4481,4485,4489],{"__ignoreMap":110},[657,4413,4414],{"class":659,"line":660},[657,4415,3624],{},[657,4417,4418],{"class":659,"line":111},[657,4419,4351],{},[657,4421,4422],{"class":659,"line":671},[657,4423,4356],{},[657,4425,4426],{"class":659,"line":677},[657,4427,4361],{},[657,4429,4430],{"class":659,"line":683},[657,4431,4432],{}," \u003Cstroke android:width=\"1dp\" android:color=\"#ffbbbbbb\"/>\n",[657,4434,4435],{"class":659,"line":732},[657,4436,4437],{}," \u003Ccorners android:topLeftRadius=\"20dp\"\n",[657,4439,4440],{"class":659,"line":738},[657,4441,4442],{}," android:topRightRadius=\"20dp\"\n",[657,4444,4445],{"class":659,"line":744},[657,4446,4447],{}," />\n",[657,4449,4450],{"class":659,"line":750},[657,4451,4371],{},[657,4453,4454],{"class":659,"line":756},[657,4455,4376],{},[657,4457,4458],{"class":659,"line":907},[657,4459,4460],{}," \u003Citem android:top=\"1dp\" android:left=\"1dp\" android:right=\"1dp\" android:bottom=\"1dp\">\n",[657,4462,4463],{"class":659,"line":913},[657,4464,4361],{},[657,4466,4467],{"class":659,"line":919},[657,4468,4390],{},[657,4470,4471],{"class":659,"line":1006},[657,4472,4437],{},[657,4474,4475],{"class":659,"line":1088},[657,4476,4442],{},[657,4478,4479],{"class":659,"line":1094},[657,4480,4447],{},[657,4482,4483],{"class":659,"line":1100},[657,4484,4371],{},[657,4486,4487],{"class":659,"line":1106},[657,4488,4376],{},[657,4490,4491],{"class":659,"line":1112},[657,4492,4403],{},[19,4494,4495],{},"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)",[19,4497,4498],{},"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:",[649,4500,4502],{"className":651,"code":4501,"language":653,"meta":110,"style":110},"\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",[460,4503,4504,4508,4513,4518,4523,4527,4532,4537],{"__ignoreMap":110},[657,4505,4506],{"class":659,"line":660},[657,4507,663],{"emptyLinePlaceholder":127},[657,4509,4510],{"class":659,"line":111},[657,4511,4512],{},"\u003Cselector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n",[657,4514,4515],{"class":659,"line":671},[657,4516,4517],{}," \u003Citem android:drawable=\"@drawable/rounded_corner_top_click\"\n",[657,4519,4520],{"class":659,"line":677},[657,4521,4522],{}," android:state_pressed=\"true\"/>\n",[657,4524,4525],{"class":659,"line":683},[657,4526,4517],{},[657,4528,4529],{"class":659,"line":732},[657,4530,4531],{}," android:state_focused=\"true\"/>\n",[657,4533,4534],{"class":659,"line":738},[657,4535,4536],{}," \u003Citem android:drawable=\"@drawable/rounded_corner_top\"/>\n",[657,4538,4539],{"class":659,"line":744},[657,4540,4541],{},"\u003C/selector>\n",[19,4543,4544],{},"Now do the same for the other backgrounds of the list.",[19,4546,4547],{},"All that is left now, is to assign the right backgrounds in our ListAdapter like following:",[649,4549,4551],{"className":2130,"code":4550,"language":2132,"meta":110,"style":110},"@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",[460,4552,4553,4557,4562,4567,4572,4577,4582,4587,4592,4597,4602,4607,4612,4616,4620,4625],{"__ignoreMap":110},[657,4554,4555],{"class":659,"line":660},[657,4556,2143],{},[657,4558,4559],{"class":659,"line":111},[657,4560,4561],{},"public View getView(int position, View convertView, ViewGroup parent) {\n",[657,4563,4564],{"class":659,"line":671},[657,4565,4566],{}," //...\n",[657,4568,4569],{"class":659,"line":677},[657,4570,4571],{}," //skipping the view reuse stuff\n",[657,4573,4574],{"class":659,"line":683},[657,4575,4576],{}," if (position == 0 && entry_list.size() == 1) {\n",[657,4578,4579],{"class":659,"line":732},[657,4580,4581],{}," view.setBackgroundResource(R.drawable.selector_rounded_corner);\n",[657,4583,4584],{"class":659,"line":738},[657,4585,4586],{}," } else if (position == 0) {\n",[657,4588,4589],{"class":659,"line":744},[657,4590,4591],{}," view.setBackgroundResource(R.drawable.selector_rounded_corner_top);\n",[657,4593,4594],{"class":659,"line":750},[657,4595,4596],{}," } else if (position == entry_list.size() - 1) {\n",[657,4598,4599],{"class":659,"line":756},[657,4600,4601],{}," view.setBackgroundResource(R.drawable.selector_rounded_corner_bottom);\n",[657,4603,4604],{"class":659,"line":907},[657,4605,4606],{}," } else {\n",[657,4608,4609],{"class":659,"line":913},[657,4610,4611],{}," view.setBackgroundResource(R.drawable.selector_middle);\n",[657,4613,4614],{"class":659,"line":919},[657,4615,2188],{},[657,4617,4618],{"class":659,"line":1006},[657,4619,4566],{},[657,4621,4622],{"class":659,"line":1088},[657,4623,4624],{}," //skipping the filling of the view\n",[657,4626,4627],{"class":659,"line":1094},[657,4628,2438],{},[19,4630,4631],{},"Aaaand we’re done.",[1341,4633,2074],{},{"title":110,"searchDepth":111,"depth":111,"links":4635},[],[120,483],"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":4318,"description":4327},"blog/android-listview-with-rounded-corners",[133,4077,4645,4078],"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":4649,"title":4650,"author":4651,"body":4652,"category":4945,"date":4946,"description":4947,"extension":124,"link":4948,"meta":4949,"navigation":127,"path":4950,"seo":4951,"slug":4656,"stem":4952,"tags":4953,"teaser":4956,"__hash__":4957},"blog/blog/evaluating-mobile-multiplatform-frameworks.md","Evaluating Mobile Multiplatform Frameworks",[617],{"type":12,"value":4653,"toc":4943},[4654,4657,4660,4684,4687,4699,4702,4705,4708,4765,4774,4925,4928,4931,4934,4937,4940],[15,4655,4650],{"id":4656},"evaluating-mobile-multiplatform-frameworks",[19,4658,4659],{},"For an upcoming, probably large mobile project, I was asked to look at the current situation on mobile multiplatform\nframeworks that cover at least Android and iOS and provide access to some native API’s like the camera. So I looked at\nseveral of the available frameworks, but only two of them fulfilled all requirements while also providing advantages\ntowards other ones.",[19,4661,4662,4666,4667,4672,4673,4678,4679],{},[33,4663],{"alt":4664,"src":4665},"\"phonegap_logo\"","https://media.synyx.de/uploads//2011/05/phonegap_symbian.jpg","\nFirst there is PhoneGap (",[152,4668,4671],{"href":4669,"rel":4670},"https://web.archive.org/web/20210302121558/https://phonegap.com/",[156],"http://www.phonegap.com/",")\nwhich additionally provides you a buildservice in their\ncloud (",[152,4674,4677],{"href":4675,"rel":4676},"https://web.archive.org/web/20210502104931/https://build.phonegap.com/",[156],"https://build.phonegap.com/"," – currently\nin beta), to which you can upload your project and it builds your Apps for Android, iOS, BlackBerry OS, Palm OS and\nSymbian. PhoneGap furthermore supports Windows Mobile and will be supporting Bada and MeeGo in the future. For an\noverview of the supported features on the different platforms, check this\nsite: ",[152,4680,4683],{"href":4681,"rel":4682},"https://web.archive.org/web/20120512021720/http://phonegap.com/about/features/",[156],"http://www.phonegap.com/features",[19,4685,4686],{},"With PhoneGap you write your App in HTML, CSS and Javascript and can also access platform dependent API’s through the\nframework (You can write your own native classes and access them as well, but you have to provide them for every\nplatform). The App runs inside the browser and so it doesn’t feel like an app, but more like a website. Moreover, you\nstyle it like a website, which makes it easier to design it for all the different platforms and display resolutions, and\nPhoneGap lets you even use your own JS-Framworks to do so.",[19,4688,4689,4690,4694,4695],{},"Then there’s Titanium (",[152,4691,4692],{"href":4692,"rel":4693},"http://www.appcelerator.com/",[156],"), with which you also write your app in HTML, CSS and JS, but in\nterms of JS you can only use the Titanium provided JS-Framework and no others. It only supports Android and iOS, but\nprovides you access to the native UI elements of both platforms and so doesn’t need to run in the browser, which is a\nbig benefit to the Apps look&feel as well as to the performance. Unfortunately there’s also a downside to that: based\non a few feedbacks from other projects, you have to differ between Android and iOS code on many occasions, because the\nUI elements are different or some of them only work on one of the\nplatforms.",[33,4696],{"alt":4697,"src":4698},"\"titanium_logo\"","https://media.synyx.de/uploads//2011/05/titanium_logo.png",[19,4700,4701],{},"And what’s more, I didn’t even get the Titanium showcase App (KitchenSink) running in the first place. At the beginning\nthere were several errors with the provided IDE that is needed to build the Apps. As the errors were cleared (took some\ntime to find the solutions in the forum and elsewhere), I could build the App, but it only got to the splashscreen – and\nthen it did nothing. I then decided to download it from someone who built it some time ago and it ran very smoothly and\nprovided access to all kind of features. But nevertheless, I couldn’t try it out myself, because it didn’t work for me.",[19,4703,4704],{},"So only PhoneGap was left and I decided to compare it effort- and performance wise with a Java coded Android App. I\nimplemented some basic elements in both apps, like Textfields, Lists (with much data, because the upcoming project will\nmost likely need that), dynamic SelectFields, Images, and of course multiple Activities/Pages.",[19,4706,4707],{},"What I noticed immediately is the difference in responsiveness of the elements. The ones in the browser need remarkably\nlonger to react on the users touch. Other than that, the App by itselfs needs much longer to load with PhoneGap. This\ncan be traced to the way transition animations are done in PhoneGap, according to the PhoneGap tutorials:",[649,4709,4711],{"className":787,"code":4710,"language":789,"meta":110,"style":110},"\u003Cdiv class=\"page\" id=\"page1\">...\u003C/div>\n\u003Cdiv class=\"page\" id=\"page2\">...\u003C/div>\n",[460,4712,4713,4740],{"__ignoreMap":110},[657,4714,4715,4717,4719,4721,4723,4726,4728,4730,4733,4736,4738],{"class":659,"line":660},[657,4716,797],{"class":796},[657,4718,3008],{"class":2834},[657,4720,3011],{"class":2838},[657,4722,2872],{"class":796},[657,4724,4725],{"class":2875},"\"page\"",[657,4727,3154],{"class":2838},[657,4729,2872],{"class":796},[657,4731,4732],{"class":2875},"\"page1\"",[657,4734,4735],{"class":796},">...\u003C/",[657,4737,3008],{"class":2834},[657,4739,804],{"class":796},[657,4741,4742,4744,4746,4748,4750,4752,4754,4756,4759,4761,4763],{"class":659,"line":111},[657,4743,797],{"class":796},[657,4745,3008],{"class":2834},[657,4747,3011],{"class":2838},[657,4749,2872],{"class":796},[657,4751,4725],{"class":2875},[657,4753,3154],{"class":2838},[657,4755,2872],{"class":796},[657,4757,4758],{"class":2875},"\"page2\"",[657,4760,4735],{"class":796},[657,4762,3008],{"class":2834},[657,4764,804],{"class":796},[19,4766,4767,4768,4773],{},"You have to declare the different pages in the same HTML file, with all but one styled as “display: none”. Then you\nswitch between them via javascript (",[152,4769,4772],{"href":4770,"rel":4771},"https://jqueryui.com/",[156],"jQueryUI","‘s hide- and show-methods for example).",[649,4775,4779],{"className":4776,"code":4777,"language":4778,"meta":110,"style":110},"language-javascript shiki shiki-themes github-light github-dark","function switchView(hideView, showView, hideDirection, showDirection) {\n $(\"#\" + hideView).hide(\n \"slide\",\n {\n direction: hideDirection,\n },\n 250,\n );\n $(\"#\" + showView).show(\n \"slide\",\n {\n direction: showDirection,\n },\n 250,\n );\n}\n","javascript",[460,4780,4781,4816,4838,4846,4851,4856,4861,4869,4874,4892,4898,4902,4907,4911,4917,4921],{"__ignoreMap":110},[657,4782,4783,4787,4790,4793,4797,4800,4803,4805,4808,4810,4813],{"class":659,"line":660},[657,4784,4786],{"class":4785},"szBVR","function",[657,4788,4789],{"class":2838}," switchView",[657,4791,4792],{"class":796},"(",[657,4794,4796],{"class":4795},"s4XuR","hideView",[657,4798,4799],{"class":796},", ",[657,4801,4802],{"class":4795},"showView",[657,4804,4799],{"class":796},[657,4806,4807],{"class":4795},"hideDirection",[657,4809,4799],{"class":796},[657,4811,4812],{"class":4795},"showDirection",[657,4814,4815],{"class":796},") {\n",[657,4817,4818,4821,4823,4826,4829,4832,4835],{"class":659,"line":111},[657,4819,4820],{"class":2838}," $",[657,4822,4792],{"class":796},[657,4824,4825],{"class":2875},"\"#\"",[657,4827,4828],{"class":4785}," +",[657,4830,4831],{"class":796}," hideView).",[657,4833,4834],{"class":2838},"hide",[657,4836,4837],{"class":796},"(\n",[657,4839,4840,4843],{"class":659,"line":671},[657,4841,4842],{"class":2875}," \"slide\"",[657,4844,4845],{"class":796},",\n",[657,4847,4848],{"class":659,"line":677},[657,4849,4850],{"class":796}," {\n",[657,4852,4853],{"class":659,"line":683},[657,4854,4855],{"class":796}," direction: hideDirection,\n",[657,4857,4858],{"class":659,"line":732},[657,4859,4860],{"class":796}," },\n",[657,4862,4863,4867],{"class":659,"line":738},[657,4864,4866],{"class":4865},"sj4cs"," 250",[657,4868,4845],{"class":796},[657,4870,4871],{"class":659,"line":744},[657,4872,4873],{"class":796}," );\n",[657,4875,4876,4878,4880,4882,4884,4887,4890],{"class":659,"line":750},[657,4877,4820],{"class":2838},[657,4879,4792],{"class":796},[657,4881,4825],{"class":2875},[657,4883,4828],{"class":4785},[657,4885,4886],{"class":796}," showView).",[657,4888,4889],{"class":2838},"show",[657,4891,4837],{"class":796},[657,4893,4894,4896],{"class":659,"line":756},[657,4895,4842],{"class":2875},[657,4897,4845],{"class":796},[657,4899,4900],{"class":659,"line":907},[657,4901,4850],{"class":796},[657,4903,4904],{"class":659,"line":913},[657,4905,4906],{"class":796}," direction: showDirection,\n",[657,4908,4909],{"class":659,"line":919},[657,4910,4860],{"class":796},[657,4912,4913,4915],{"class":659,"line":1006},[657,4914,4866],{"class":4865},[657,4916,4845],{"class":796},[657,4918,4919],{"class":659,"line":1088},[657,4920,4873],{"class":796},[657,4922,4923],{"class":659,"line":1094},[657,4924,2438],{"class":796},[19,4926,4927],{},"The problem in this case is, that multiple “pages” are being loaded right at the beginning, increasing the load time.\nAlso the transition animation with JavaScript isn’t that smooth – even with high-end smartphones.",[19,4929,4930],{},"Comparing the speed of development of my sample App, I was a little faster with PhoneGap than with the Java code. In\nterms of speed, PhoneGap is especially useful, if you’re developing your App for mulitple platforms – you will most\nlikely be a few times faster than developing them natively on each platform and you also don’t have to set up the\ndifferent IDE’s if you are using PhoneGap Build on top of that.",[19,4932,4933],{},"Like I already mentioned, the performance isn’t that great, though. I assume that you probably won’t use PhoneGap – or\nhtml and js in general – for larger Apps which you only need for one or two platforms. This is because the\nmaintainability and testability are (from my point of view as a java developer) much better with java and also in other\nlanguages than they are with javascript and html+css (Well, you DO have only one codebase here, but therefor have a set\nof different mobile browsers that you need to check it with).",[19,4935,4936],{},"In my opinion, PhoneGap only comes in handy if you want to distribute a small App for as many platforms as possible\nwhile keeping the needed effort as low as possible.",[19,4938,4939],{},"In our case, we decided not to use PhoneGap or another framework for the project, because it needs to perform well and\nwill be a bigger project where the maintainability is very important. Besides, it will only have to run on Android and (\nmaybe) iOS, which doesn’t make the benefit you gain from PhoneGap that great.",[1341,4941,4942],{},"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 .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}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}",{"title":110,"searchDepth":111,"depth":111,"links":4944},[],[120],"2011-06-27T08:52:15","For an upcoming, probably large mobile project, I was asked to look at the current situation on mobile multiplatform\\nframeworks that cover at least Android and iOS and provide access to some native API’s like the camera. So I looked at\\nseveral of the available frameworks, but only two of them fulfilled all requirements while also providing advantages\\ntowards other ones.","https://synyx.de/blog/evaluating-mobile-multiplatform-frameworks/",{},"/blog/evaluating-mobile-multiplatform-frameworks",{"title":4650,"description":4659},"blog/evaluating-mobile-multiplatform-frameworks",[133,4954,4312,4955],"ios","phonegap","For an upcoming, probably large mobile project, I was asked to look at the current situation on mobile multiplatform frameworks that cover at least Android and iOS and provide access…","UlZ2F2s_fxc2RGKJWGbsoS3h6uE_MAyirMi-b2IUjWE",{"id":4959,"title":4960,"author":4961,"body":4963,"category":5005,"date":5006,"description":5007,"extension":124,"link":5008,"meta":5009,"navigation":127,"path":5010,"seo":5011,"slug":4967,"stem":5013,"tags":5014,"teaser":5017,"__hash__":5018},"blog/blog/synyx-at-the-droidcon-2011.md","Synyx at the Droidcon 2011",[4962],"krupicka",{"type":12,"value":4964,"toc":5003},[4965,4968,4975,4988,4991,4994],[15,4966,4960],{"id":4967},"synyx-at-the-droidcon-2011",[19,4969,4970,4971,4974],{},"It has been a while since these pages saw some content. The daily software engineering business calls for our full\nattention, leaving us writing more code than content. ",[693,4972,4973],{},"Which is absolutely a good thing!"," Nevertheless we want to show\nyou, that we are still pretty much alive.",[19,4976,4977,4978,4983,4984,4987],{},"While still working on some new articles, we are at this very moment in Berlin to visit\nthe ",[152,4979,4982],{"href":4980,"rel":4981},"https://www.droidcon.com/",[156],"Droidcon"," conference. From ",[693,4985,4986],{},"March 23rd to 24th",", we will be attending the barcamp\nsessions & talks. It will be our pleasure to meet familiar faces as also new people there and we are looking forward to\ninteresting discussions.",[19,4989,4990],{},"We wish everyone attending a good time there!",[19,4992,4993],{},"— Florian Krupicka & Tobias Knell",[19,4995,4996,4997,5002],{},"PS: If you are more interested in database storage beyond SQL, you will also be able to meet even more members of the\nSynyx family at the ",[152,4998,5001],{"href":4999,"rel":5000},"http://www.10gen.com/conferences/mongoberlin2011",[156],"MongoBerlin"," conference on Friday, the 25th of\nMarch.",{"title":110,"searchDepth":111,"depth":111,"links":5004},[],[120],"2011-03-23T02:24:43","It has been a while since these pages saw some content. The daily software engineering business calls for our full\\nattention, leaving us writing more code than content. Which is absolutely a good thing! Nevertheless we want to show\\nyou, that we are still pretty much alive.","https://synyx.de/blog/synyx-at-the-droidcon-2011/",{},"/blog/synyx-at-the-droidcon-2011",{"title":4960,"description":5012},"It has been a while since these pages saw some content. The daily software engineering business calls for our full\nattention, leaving us writing more code than content. Which is absolutely a good thing! Nevertheless we want to show\nyou, that we are still pretty much alive.","blog/synyx-at-the-droidcon-2011",[5015,5016,134],"berlin","conference","It has been a while since these pages saw some content. The daily software engineering business calls for our full attention, leaving us writing more code than content. Which is…","rgaqFjx5_-Ru7Rq2afSUOyG-0MdF2mgHboSi2lNAiPw",{"id":5020,"title":5021,"author":5022,"body":5024,"category":5037,"date":5038,"description":110,"extension":124,"link":5039,"meta":5040,"navigation":127,"path":5041,"seo":5042,"slug":5043,"stem":5044,"tags":5045,"teaser":5049,"__hash__":5050},"blog/blog/i-think-i-spider-2-0.md","I think I spider 2.0",[5023],"linsin",{"type":12,"value":5025,"toc":5035},[5026,5029],[15,5027,5021],{"id":5028},"i-think-i-spider-20",[19,5030,5031],{},[33,5032],{"alt":5033,"src":5034},"I Think I Spider","https://media.synyx.de/uploads//2010/09/app-icon-512px.png",{"title":110,"searchDepth":111,"depth":111,"links":5036},[],[120,263],"2011-01-31T12:36:40","https://synyx.de/blog/i-think-i-spider-2-0/",{},"/blog/i-think-i-spider-2-0",{"title":5021,"description":110},"i-think-i-spider-2-0","blog/i-think-i-spider-2-0",[5046,5047,5048],"apple","iphone","ipod","It has been a while since our 1.0 release of I think I spider, but we have been working hard on a new major release with a couple of great…","wQyb1IaF6l-gmasnEesnmZdzAba4HTg7N-UWdSnfvPI",{"id":5052,"title":5053,"author":5054,"body":5055,"category":5331,"date":5332,"description":5333,"extension":124,"link":5334,"meta":5335,"navigation":127,"path":5336,"seo":5337,"slug":5059,"stem":5339,"tags":5340,"teaser":5343,"__hash__":5344},"blog/blog/ui-test-automation.md","UI Test Automation",[5023],{"type":12,"value":5056,"toc":5329},[5057,5060,5075,5084,5093,5096,5290,5297,5300,5323,5326],[15,5058,5053],{"id":5059},"ui-test-automation",[19,5061,5062,5063,5068,5069,5074],{},"One of the most impressive talks for me at ",[152,5064,5067],{"href":5065,"rel":5066},"http://mobile.synyx.de/2010/06/wwdc-2010/",[156],"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 ",[152,5070,5073],{"href":5071,"rel":5072},"http://mobile.synyx.de/2010/09/i-think-i-spider-1-0-released/",[156],"very own App “I think I spider”",".",[19,5076,5077,5078,5083],{},"All you need to get started is an App, Instruments and some basic JavaScript skills. Apple provides a set\nof ",[152,5079,5082],{"href":5080,"rel":5081},"http://developer.apple.com/library/ios/#documentation/ToolsLanguages/Reference/UIATargetClassReference/UIATargetClass/UIATargetClass.html",[156],"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.",[19,5085,5086,5087,5092],{},"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 ",[152,5088,5091],{"href":5089,"rel":5090},"https://developer.apple.com/library/mac/#documentation/DeveloperTools/Conceptual/InstrumentsUserGuide/Built-InInstruments/Built-InInstruments.html%23//apple_ref/doc/uid/TP40004652-CH6-SW75",[156],"documentation","\nis quite solid, as most of them are, and explains the process in detail.",[19,5094,5095],{},"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:",[649,5097,5099],{"className":4776,"code":5098,"language":4778,"meta":110,"style":110},"// 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",[460,5100,5101,5107,5126,5149,5161,5166,5181,5192,5207,5233,5256,5266,5277,5286],{"__ignoreMap":110},[657,5102,5103],{"class":659,"line":660},[657,5104,5106],{"class":5105},"sJ8bj","// setup\n",[657,5108,5109,5112,5115,5117,5120,5123],{"class":659,"line":111},[657,5110,5111],{"class":4785},"var",[657,5113,5114],{"class":796}," target ",[657,5116,2872],{"class":4785},[657,5118,5119],{"class":796}," UIATarget.",[657,5121,5122],{"class":2838},"localTarget",[657,5124,5125],{"class":796},"();\n",[657,5127,5128,5130,5133,5135,5138,5141,5144,5147],{"class":659,"line":671},[657,5129,5111],{"class":4785},[657,5131,5132],{"class":796}," appWindow ",[657,5134,2872],{"class":4785},[657,5136,5137],{"class":796}," target.",[657,5139,5140],{"class":2838},"frontMostApp",[657,5142,5143],{"class":796},"().",[657,5145,5146],{"class":2838},"mainWindow",[657,5148,5125],{"class":796},[657,5150,5151,5153,5156,5158],{"class":659,"line":677},[657,5152,5111],{"class":4785},[657,5154,5155],{"class":796}," element ",[657,5157,2872],{"class":4785},[657,5159,5160],{"class":796}," target;\n",[657,5162,5163],{"class":659,"line":683},[657,5164,5165],{"class":5105},"// first test\n",[657,5167,5168,5170,5173,5175,5178],{"class":659,"line":732},[657,5169,5111],{"class":4785},[657,5171,5172],{"class":796}," testName ",[657,5174,2872],{"class":4785},[657,5176,5177],{"class":2875}," \"Start Screen Test\"",[657,5179,5180],{"class":796},";\n",[657,5182,5183,5186,5189],{"class":659,"line":738},[657,5184,5185],{"class":796},"UIALogger.",[657,5187,5188],{"class":2838},"logStart",[657,5190,5191],{"class":796},"(testName);\n",[657,5193,5194,5196,5199,5201,5204],{"class":659,"line":744},[657,5195,5185],{"class":796},[657,5197,5198],{"class":2838},"logMessage",[657,5200,4792],{"class":796},[657,5202,5203],{"class":2875},"\"Tapping start screen\"",[657,5205,5206],{"class":796},");\n",[657,5208,5209,5212,5215,5218,5221,5224,5227,5230],{"class":659,"line":750},[657,5210,5211],{"class":796},"appWindow.",[657,5213,5214],{"class":2838},"elements",[657,5216,5217],{"class":796},"()[",[657,5219,5220],{"class":2875},"\"start_screen\"",[657,5222,5223],{"class":796},"].",[657,5225,5226],{"class":2838},"tap",[657,5228,5229],{"class":796},"(); ",[657,5231,5232],{"class":5105},"// open the book\n",[657,5234,5235,5238,5241,5243,5245,5248,5250,5253],{"class":659,"line":756},[657,5236,5237],{"class":4785},"if",[657,5239,5240],{"class":796}," (appWindow.",[657,5242,5214],{"class":2838},[657,5244,5217],{"class":796},[657,5246,5247],{"class":2875},"\"main_screen\"",[657,5249,5223],{"class":796},[657,5251,5252],{"class":2838},"isValid",[657,5254,5255],{"class":796},"()) {\n",[657,5257,5258,5261,5264],{"class":659,"line":907},[657,5259,5260],{"class":796}," UIALogger.",[657,5262,5263],{"class":2838},"logFail",[657,5265,5191],{"class":796},[657,5267,5268,5271,5274],{"class":659,"line":913},[657,5269,5270],{"class":796},"} ",[657,5272,5273],{"class":4785},"else",[657,5275,5276],{"class":796}," {\n",[657,5278,5279,5281,5284],{"class":659,"line":919},[657,5280,5260],{"class":796},[657,5282,5283],{"class":2838},"logPass",[657,5285,5191],{"class":796},[657,5287,5288],{"class":659,"line":1006},[657,5289,2438],{"class":796},[19,5291,5292,5293,5296],{},"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 ",[693,5294,5295],{},"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.",[19,5298,5299],{},"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.",[19,5301,5302,5303,5308,5309,5314,5315,5318,5319,5322],{},"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 ",[152,5304,5307],{"href":5305,"rel":5306},"http://blog.airsource.co.uk/index.php/2010/08/13/ui-automation-on-the-iphone/",[156],"summarized","\nby ",[152,5310,5313],{"href":5311,"rel":5312},"http://www.airsource.co.uk",[156],"Air Source",". For us, a missing ",[693,5316,5317],{},"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 ",[693,5320,5321],{},"UIALogger.logMessage"," for those cases as a workaround, but it’s quite easy to miss those\nlines, since they don’t stand out.",[19,5324,5325],{},"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!",[1341,5327,5328],{},"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":110,"searchDepth":111,"depth":111,"links":5330},[],[120,483],"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":5053,"description":5338},"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",[5046,5341,5047,5048,5342],"ipad","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":5346,"title":5347,"author":5348,"body":5349,"category":5362,"date":5363,"description":110,"extension":124,"link":5364,"meta":5365,"navigation":127,"path":5366,"seo":5367,"slug":5353,"stem":5368,"tags":5369,"teaser":5371,"__hash__":5372},"blog/blog/game-center-presentation-at-cocoa-heads-karlsruhe.md","Game Center Presentation at Cocoa Heads Karlsruhe",[5023],{"type":12,"value":5350,"toc":5360},[5351,5354],[15,5352,5347],{"id":5353},"game-center-presentation-at-cocoa-heads-karlsruhe",[19,5355,5356],{},[33,5357],{"alt":5358,"src":5359},"hero-gamecenter.png","https://media.synyx.de/uploads//2010/11/hero-gamecenter.png",{"title":110,"searchDepth":111,"depth":111,"links":5361},[],[120],"2010-11-19T09:56:35","https://synyx.de/blog/game-center-presentation-at-cocoa-heads-karlsruhe/",{},"/blog/game-center-presentation-at-cocoa-heads-karlsruhe",{"title":5347,"description":110},"blog/game-center-presentation-at-cocoa-heads-karlsruhe",[5046,5341,5047,5048,5370],"objective-c","On Wednesday, I’ll be giving a presentation on Apple’s Game Center at the local CocoaHeads Group, here in Karlsruhe. Game Center is Apple’s social gaming network, that lets you invite…","GiUsd6F3L7seTHDgO_RaxV4qb-rQR5cfhuleZQHMNhQ",{"id":5374,"title":5375,"author":5376,"body":5377,"category":5844,"date":5845,"description":5846,"extension":124,"link":5847,"meta":5848,"navigation":127,"path":5849,"seo":5850,"slug":5381,"stem":5852,"tags":5853,"teaser":5856,"__hash__":5857},"blog/blog/android-roboguice-against-oncreate-boilerplate.md","Android: RoboGuice against onCreate boilerplate",[4962],{"type":12,"value":5378,"toc":5838},[5379,5382,5393,5397,5425,5429,5432,5617,5623,5769,5775,5779,5817,5821,5833,5836],[15,5380,5375],{"id":5381},"android-roboguice-against-oncreate-boilerplate",[19,5383,5384,5385,5388,5389,5392],{},"When prototyping Android activities with a lot of view elements, the ",[460,5386,5387],{},"onCreate"," method can quickly become cluttered.\nSetup code that simply retrieves the views from the declarative layout (by using ",[460,5390,5391],{},"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!",[23,5394,5396],{"id":5395},"enter-dependency-injection","Enter dependency injection",[19,5398,5399,5400,5405,5406,5409,5410,5415,5416,5421,5422,5424],{},"A popular design pattern — usually accompanied by a framework — to get rid of object setup clutter\nis ",[152,5401,5404],{"href":5402,"rel":5403},"http://en.wikipedia.org/wiki/Dependency_injection",[156],"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 ",[693,5407,5408],{},"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 ",[152,5411,5414],{"href":5412,"rel":5413},"http://code.google.com/p/google-guice/",[156],"Google Guice",". For the latter a young but promising add-on\nlibrary has come to fruition: ",[152,5417,5420],{"href":5418,"rel":5419},"http://code.google.com/p/roboguice/",[156],"RoboGuice"," makes DI available for your Android\nclasses. We will walk you through a small example, showing how it simplifies your ",[460,5423,5387],{}," 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.",[23,5426,5428],{"id":5427},"before-and-after","Before and after",[19,5430,5431],{},"We start right away with a small code snippet from an Android activity:",[649,5433,5435],{"className":2130,"code":5434,"language":2132,"meta":110,"style":110},"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",[460,5436,5437,5442,5447,5452,5457,5462,5467,5472,5477,5482,5487,5492,5497,5502,5506,5510,5515,5520,5525,5530,5535,5540,5545,5550,5555,5560,5565,5570,5575,5580,5585,5589,5594,5599,5604,5609,5613],{"__ignoreMap":110},[657,5438,5439],{"class":659,"line":660},[657,5440,5441],{},"public class SpiderActivity extends Activity {\n",[657,5443,5444],{"class":659,"line":111},[657,5445,5446],{}," // [..snip..] lots of other members not needed for the example\n",[657,5448,5449],{"class":659,"line":671},[657,5450,5451],{}," private RelativeLayout rating;\n",[657,5453,5454],{"class":659,"line":677},[657,5455,5456],{}," private ImageView ratingStars;\n",[657,5458,5459],{"class":659,"line":683},[657,5460,5461],{}," private Button infoButton;\n",[657,5463,5464],{"class":659,"line":732},[657,5465,5466],{}," private TextView type;\n",[657,5468,5469],{"class":659,"line":738},[657,5470,5471],{}," private TextView language;\n",[657,5473,5474],{"class":659,"line":744},[657,5475,5476],{}," private TextView spider;\n",[657,5478,5479],{"class":659,"line":750},[657,5480,5481],{}," private TextView translation;\n",[657,5483,5484],{"class":659,"line":756},[657,5485,5486],{}," private TextView author;\n",[657,5488,5489],{"class":659,"line":907},[657,5490,5491],{}," private TextView page;\n",[657,5493,5494],{"class":659,"line":913},[657,5495,5496],{}," private RatingBar ratingBar;\n",[657,5498,5499],{"class":659,"line":919},[657,5500,5501],{}," private Handler handler;\n",[657,5503,5504],{"class":659,"line":1006},[657,5505,2148],{},[657,5507,5508],{"class":659,"line":1088},[657,5509,2183],{},[657,5511,5512],{"class":659,"line":1094},[657,5513,5514],{}," setContentView(R.layout.main);\n",[657,5516,5517],{"class":659,"line":1100},[657,5518,5519],{}," infoButton = (Button) findViewById(R.id.InfoButton);\n",[657,5521,5522],{"class":659,"line":1106},[657,5523,5524],{}," infoButton.setOnClickListener(new InfosOnClickListener());\n",[657,5526,5527],{"class":659,"line":1112},[657,5528,5529],{}," rating = (RelativeLayout) findViewById(R.id.ratingDialog);\n",[657,5531,5532],{"class":659,"line":1118},[657,5533,5534],{}," ratingStars = (ImageView) findViewById(R.id.ratingStars);\n",[657,5536,5537],{"class":659,"line":1124},[657,5538,5539],{}," ratingStars.setOnClickListener(new MenuOnClickListener());\n",[657,5541,5542],{"class":659,"line":1130},[657,5543,5544],{}," type = (TextView) findViewById(R.id.TypeText);\n",[657,5546,5547],{"class":659,"line":1136},[657,5548,5549],{}," language = (TextView) findViewById(R.id.LanguageText);\n",[657,5551,5552],{"class":659,"line":1142},[657,5553,5554],{}," spider = (TextView) findViewById(R.id.SpiderText);\n",[657,5556,5557],{"class":659,"line":1148},[657,5558,5559],{}," translation = (TextView) findViewById(R.id.TranslationText);\n",[657,5561,5562],{"class":659,"line":1154},[657,5563,5564],{}," author = (TextView) findViewById(R.id.AuthorText);\n",[657,5566,5567],{"class":659,"line":1160},[657,5568,5569],{}," page = (TextView) findViewById(R.id.PageText);\n",[657,5571,5572],{"class":659,"line":1166},[657,5573,5574],{}," ratingBar = (RatingBar) findViewById(R.id.ratingBar);\n",[657,5576,5577],{"class":659,"line":1172},[657,5578,5579],{}," if (hasUnusualDeviceDimensions()) {\n",[657,5581,5582],{"class":659,"line":1178},[657,5583,5584],{}," adjustLayout();\n",[657,5586,5587],{"class":659,"line":1183},[657,5588,2178],{},[657,5590,5591],{"class":659,"line":1188},[657,5592,5593],{}," if (!isLayoutSupported()) {\n",[657,5595,5596],{"class":659,"line":1194},[657,5597,5598],{}," Toast.makeText(this.getApplicationContext(),\n",[657,5600,5601],{"class":659,"line":1199},[657,5602,5603],{}," \"Sorry, this phone resolution is not supported.\",\n",[657,5605,5606],{"class":659,"line":1205},[657,5607,5608],{}," Toast.LENGTH_LONG).show();\n",[657,5610,5611],{"class":659,"line":1210},[657,5612,2178],{},[657,5614,5615],{"class":659,"line":1215},[657,5616,2188],{},[19,5618,5619,5620,5622],{},"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 ",[460,5621,5387],{}," method. Let’s see how this would look like with RoboGuice\napplied to your project:",[649,5624,5626],{"className":2130,"code":5625,"language":2132,"meta":110,"style":110},"// 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",[460,5627,5628,5633,5638,5643,5648,5653,5658,5663,5668,5673,5678,5683,5688,5693,5698,5703,5707,5711,5716,5720,5725,5729,5733,5737,5741,5745,5749,5753,5757,5761,5765],{"__ignoreMap":110},[657,5629,5630],{"class":659,"line":660},[657,5631,5632],{},"// these are the needed classes from the RoboGuice framework\n",[657,5634,5635],{"class":659,"line":111},[657,5636,5637],{},"import roboguice.activity.RoboActivity;\n",[657,5639,5640],{"class":659,"line":671},[657,5641,5642],{},"import roboguice.inject.InjectView;\n",[657,5644,5645],{"class":659,"line":677},[657,5646,5647],{},"// [..snip..]\n",[657,5649,5650],{"class":659,"line":683},[657,5651,5652],{},"public class SpiderActivity extends RoboActivity {\n",[657,5654,5655],{"class":659,"line":732},[657,5656,5657],{}," @InjectView(R.id.ratingDialog ) private RelativeLayout rating;\n",[657,5659,5660],{"class":659,"line":738},[657,5661,5662],{}," @InjectView(R.id.ratingStars) private ImageView ratingStars;\n",[657,5664,5665],{"class":659,"line":744},[657,5666,5667],{}," @InjectView(R.id.InfoButton) private Button infoButton;\n",[657,5669,5670],{"class":659,"line":750},[657,5671,5672],{}," @InjectView(R.id.TypeText) private TextView type;\n",[657,5674,5675],{"class":659,"line":756},[657,5676,5677],{}," @InjectView(R.id.LanguageText) private TextView language;\n",[657,5679,5680],{"class":659,"line":907},[657,5681,5682],{}," @InjectView(R.id.SpiderText) private TextView spider;\n",[657,5684,5685],{"class":659,"line":913},[657,5686,5687],{}," @InjectView(R.id.TranslationText) private TextView translation;\n",[657,5689,5690],{"class":659,"line":919},[657,5691,5692],{}," @InjectView(R.id.AuthorText) private TextView author;\n",[657,5694,5695],{"class":659,"line":1006},[657,5696,5697],{}," @InjectView(R.id.PageText) private TextView page;\n",[657,5699,5700],{"class":659,"line":1088},[657,5701,5702],{}," @InjectView(R.id.ratingBar) private RatingBar ratingBar;\n",[657,5704,5705],{"class":659,"line":1094},[657,5706,2148],{},[657,5708,5709],{"class":659,"line":1100},[657,5710,2183],{},[657,5712,5713],{"class":659,"line":1106},[657,5714,5715],{}," // the following call actually injects your views ...\n",[657,5717,5718],{"class":659,"line":1112},[657,5719,5514],{},[657,5721,5722],{"class":659,"line":1118},[657,5723,5724],{}," // they are available afterwards\n",[657,5726,5727],{"class":659,"line":1124},[657,5728,5524],{},[657,5730,5731],{"class":659,"line":1130},[657,5732,5539],{},[657,5734,5735],{"class":659,"line":1136},[657,5736,5579],{},[657,5738,5739],{"class":659,"line":1142},[657,5740,5584],{},[657,5742,5743],{"class":659,"line":1148},[657,5744,2178],{},[657,5746,5747],{"class":659,"line":1154},[657,5748,5593],{},[657,5750,5751],{"class":659,"line":1160},[657,5752,5598],{},[657,5754,5755],{"class":659,"line":1166},[657,5756,5603],{},[657,5758,5759],{"class":659,"line":1172},[657,5760,5608],{},[657,5762,5763],{"class":659,"line":1178},[657,5764,2178],{},[657,5766,5767],{"class":659,"line":1183},[657,5768,2188],{},[19,5770,5771,5772,5774],{},"See how the ",[460,5773,5387],{}," 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.",[23,5776,5778],{"id":5777},"setting-up-roboguice","Setting up RoboGuice",[19,5780,5781,5782,5787,5788,5792,5793,5796,5797,5800,5801,5804,5805,5808,5809,5812,5813,5816],{},"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 ",[152,5783,5786],{"href":5784,"rel":5785},"https://web.archive.org/web/20160713065637/http://google-guice.googlecode.com:80/files/guice-2.0-no_aop.jar",[156],"Guice 2.0"," (\nwhich is the base for RoboGuice)\nand ",[152,5789,5420],{"href":5790,"rel":5791},"http://download.java.net/maven/2/roboguice/roboguice/1.1-SNAPSHOT/roboguice-1.1-20100408.222944-3.jar",[156]," (\nin our example we used the development version 1.1) into a ",[460,5794,5795],{},"lib"," subdirectory of your project. Add both the libraries\nto your build path by selecting ",[164,5798,5799],{},"Build Path > Add to Build Path"," from the context menu of each of the libraries.\nFinally a small change to your ",[460,5802,5803],{},"AndroidManifest.xml"," will be needed, to make RoboGuice work. In the ",[164,5806,5807],{},"Application"," tab\nset the ",[164,5810,5811],{},"Name"," attribute to ",[460,5814,5815],{},"roboguice.application.RoboApplication"," (or make your own Application class inherit from\nit).",[23,5818,5820],{"id":5819},"finishing-words","Finishing words",[19,5822,5823,5824,5828,5829,5832],{},"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 ",[152,5825,5827],{"href":5412,"rel":5826},[156],"Guice"," pages introduce the general idioms of the framework, while\nthe ",[152,5830,5420],{"href":5418,"rel":5831},[156]," pages contain some more examples and the documentation.",[19,5834,5835],{},"I hope this short introduction proves useful and helps you making your applications code more readable and testable in\nthe end.",[1341,5837,2074],{},{"title":110,"searchDepth":111,"depth":111,"links":5839},[5840,5841,5842,5843],{"id":5395,"depth":111,"text":5396},{"id":5427,"depth":111,"text":5428},{"id":5777,"depth":111,"text":5778},{"id":5819,"depth":111,"text":5820},[120,483],"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":5375,"description":5851},"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",[133,5854,5855,483],"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":5859,"title":5860,"author":5861,"body":5862,"category":5874,"date":5875,"description":110,"extension":124,"link":5876,"meta":5877,"navigation":127,"path":5878,"seo":5879,"slug":5866,"stem":5880,"tags":5881,"teaser":5884,"__hash__":5885},"blog/blog/apn-device-tokens.md","APN Device Tokens",[5023],{"type":12,"value":5863,"toc":5872},[5864,5867],[15,5865,5860],{"id":5866},"apn-device-tokens",[19,5868,5869],{},[33,5870],{"alt":5033,"src":5871},"https://media.synyx.de/uploads//2010/07/512px.png",{"title":110,"searchDepth":111,"depth":111,"links":5873},[],[120,263,483],"2010-09-14T06:46:26","https://synyx.de/blog/apn-device-tokens/",{},"/blog/apn-device-tokens",{"title":5860,"description":110},"blog/apn-device-tokens",[5882,5046,5341,5047,5883],"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":5887,"title":5888,"author":5889,"body":5890,"category":5901,"date":5902,"description":110,"extension":124,"link":5903,"meta":5904,"navigation":127,"path":5905,"seo":5906,"slug":5907,"stem":5908,"tags":5909,"teaser":5911,"__hash__":5912},"blog/blog/i-think-i-spider-1-0-released.md","I think I spider 1.0 released",[5023],{"type":12,"value":5891,"toc":5899},[5892,5895],[15,5893,5888],{"id":5894},"i-think-i-spider-10-released",[19,5896,5897],{},[33,5898],{"alt":5033,"src":5034},{"title":110,"searchDepth":111,"depth":111,"links":5900},[],[120,263],"2010-09-11T17:54:14","https://synyx.de/blog/i-think-i-spider-1-0-released/",{},"/blog/i-think-i-spider-1-0-released",{"title":5888,"description":110},"i-think-i-spider-1-0-released","blog/i-think-i-spider-1-0-released",[133,5046,5047,5910,1362],"ithinkispider","Today we are proud to present our own very first App (it’s actually our second Android App) that made it to the App Store and Android Market – I think…","ihp4cPfv4tMEA3ebuCKmOKtft0Yrr9slF_Lsd0LB1fw",{"id":5914,"title":5915,"author":5916,"body":5917,"category":6013,"date":6014,"description":110,"extension":124,"link":6015,"meta":6016,"navigation":127,"path":6017,"seo":6018,"slug":6019,"stem":6020,"tags":6021,"teaser":6024,"__hash__":6025},"blog/blog/resolutions-on-android.md","Android resolution and layout problems",[617],{"type":12,"value":5918,"toc":6007},[5919,5922,5926,5930,5933,5936,5939,5942,5945,5949,5952,5955,5963,5966,5970,5973,5976,5979,5982,5985,5988,5991,5994,5997,6001,6004],[15,5920,5915],{"id":5921},"android-resolution-and-layout-problems",[19,5923,5924],{},[33,5925],{"alt":5033,"src":5871},[23,5927,5929],{"id":5928},"dp-or-not-dp","dp or not dp?",[19,5931,5932],{},"It was some work, but after a little time, the layout fitted for each density – well, at least so it seemed…",[19,5934,5935],{},"It fitted only for the three default dpi values (120, 160 and 240).",[19,5937,5938],{},"If you used a larger / smaller screen with the same density, the positions didn’t match exactly any more.",[19,5940,5941],{},"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.",[19,5943,5944],{},"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…).",[23,5946,5948],{"id":5947},"changing-the-layout-in-your-code","Changing the layout in your code",[19,5950,5951],{},"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.",[19,5953,5954],{},"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:",[649,5956,5961],{"className":5957,"code":5959,"language":5960},[5958],"language-text","\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","text",[460,5962,5959],{"__ignoreMap":110},[19,5964,5965],{},"Well, maybe it isn’t a good solution, but we didn’t find any other possibility here.",[23,5967,5969],{"id":5968},"using-resource-qualifiers-for-the-different-resolutions","Using resource qualifiers for the different resolutions",[19,5971,5972],{},"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.",[19,5974,5975],{},"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.",[19,5977,5978],{},"The layouts we declare are seperated in the following folders:",[19,5980,5981],{},"layout-normal-mdpi -> 320×480",[19,5983,5984],{},"layout-normal-hdpi -> 800×480 and 854×480 (adjusted in the code)",[19,5986,5987],{},"layout-normal-ldpi -> 400×240",[19,5989,5990],{},"layout-large-mdpi -> 800×480 tablet (mostly the same as the layout-normal-hdpi layout, but needs to be in this\nfolder)",[19,5992,5993],{},"layout-small-ldpi -> 320×240",[19,5995,5996],{},"Also one -landscape folder for each of them for the landscape layout for the widget (the rest of the app is portrait\nonly)",[23,5998,6000],{"id":5999},"conclusion","Conclusion",[19,6002,6003],{},"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.",[19,6005,6006],{},"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":110,"searchDepth":111,"depth":111,"links":6008},[6009,6010,6011,6012],{"id":5928,"depth":111,"text":5929},{"id":5947,"depth":111,"text":5948},{"id":5968,"depth":111,"text":5969},{"id":5999,"depth":111,"text":6000},[120,263,483],"2010-09-08T13:28:24","https://synyx.de/blog/resolutions-on-android/",{},"/blog/resolutions-on-android",{"title":5915,"description":110},"resolutions-on-android","blog/resolutions-on-android",[133,6022,5910,2215,6023],"i-think-i-spider","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":6027,"title":6028,"author":6029,"body":6030,"category":6294,"date":6295,"description":6296,"extension":124,"link":6297,"meta":6298,"navigation":127,"path":6299,"seo":6300,"slug":6034,"stem":6302,"tags":6303,"teaser":6305,"__hash__":6306},"blog/blog/on-cross-device-mobile-development-part-2.md","On cross-device mobile development – Part 2",[4962],{"type":12,"value":6031,"toc":6287},[6032,6035,6048,6052,6075,6079,6110,6114,6138,6142,6149,6153,6156,6160,6164,6189,6193,6212,6216,6231,6235,6267,6271,6284],[15,6033,6028],{"id":6034},"on-cross-device-mobile-development-part-2",[19,6036,6037,6038,6043,6044,6047],{},"In the ",[152,6039,6042],{"href":6040,"rel":6041},"http://mobile.synyx.de/2010/08/on-cross-device-mobile-development-part-1/",[156],"previous part"," of this series we took\na look on how to develop mobile applications with plain HTML, CSS & JavaScript (furthermore refered to as ",[693,6045,6046],{},"the web\nstack","). A few pieces of absolutely laid out CSS for the views, a dash of custom jQuery events for controller code\ninvocation and standard in-browser SQLite access for the model — every aspect of a MVC application should be accounted\nfor, shouldn’t it? Not quite …",[23,6049,6051],{"id":6050},"what-does-native-have-what-web-stack-hasnt","What does native have, what web stack hasn’t?",[19,6053,6054,6055,6058,6059,6062,6063,6066,6067,6070,6071,6074],{},"If you want the answer to this question boiled down to one simple word: ",[164,6056,6057],{},"abstraction",". In an application you don’t\nwant to think in terms of ",[460,6060,6061],{},"\u003Cul>"," elements, to which you append ",[460,6064,6065],{},"\u003Cli>"," elements, styled adequately with CSS to harbor\nitems populated from SQLite statements and react to code which is painstakingly attached to every list item to react to\ndifferent user inputs. You would usually think about the problem in terms of — for example — a ",[460,6068,6069],{},"ListController"," and an\nattached ",[460,6072,6073],{},"DataSource",", freeing you from the task of layouting your list. And you don’t want to have to code all this\nboilerplate from scratch. It is not the problem you want to solve. It is something left for a framework.",[23,6076,6078],{"id":6077},"competing-with-native-sdks","Competing with native SDKs",[19,6080,6081,6082,6086,6087,6092,6093,6095,6096,6101,6102,6105,6106,6109],{},"Even before the days of mobile applications, web application developers pushed hard to create frameworks to match up\nagainst their competitors from the native world. An increasing number of those are now also reconsidering deployment on\nsmall mobile devices and are joined by new JavaScript frameworks especially tailored to mobile\ndevices. ",[152,6083,6085],{"href":4669,"rel":6084},[156],"PhoneGap"," — which we refered to already in\nthe first part of the series — is\nnow ",[152,6088,6091],{"href":6089,"rel":6090},"https://web.archive.org/web/20140122061413/http://phonegap.com/2010/07/19/it%e2%80%99s-easier-than-ever-for-symbian-developers-to-build-mobile-apps-with-phonegap/",[156],"fully embraced","\nby Nokia for their Symbian smartphone operating system. Palm has his whole mobile user experience built around ",[693,6094,6046],{},", accordingly naming their operating system ",[152,6097,6100],{"href":6098,"rel":6099},"http://developer.palm.com/",[156],"WebOS",". The need for mobile ",[693,6103,6104],{},"web stack","\nframeworks is growing. The rest of this article will introduce some of those frameworks, but first a short recap of what\n",[164,6107,6108],{},"abstractions"," we would expect from such a framework, compared to their native siblings:",[160,6111,6113],{"id":6112},"models","Models",[19,6115,6116,6117,6122,6123,6130,6131,5074],{},"The way the data is retrieved and stored on the device comprises our data model. Because of constraints in memory\nfootprint and also because of security considerations, mobile SDKs should try to provide abstraction layers for your\ndata storage. As an example, the iPhone SDK incorporates a subsystem\ncalled ",[152,6118,6121],{"href":6119,"rel":6120},"http://developer.apple.com/iphone/library/documentation/DataManagement/Conceptual/iPhoneCoreData01/",[156],"CoreData",".\nIt provides modelling tools and stub generation to easily integrate with ",[152,6124,6127],{"href":6125,"rel":6126},"http://developer.apple.com/iphone/library/documentation/CoreData/Reference/NSFetchedResultsController_Class/Reference/Reference.html",[156],[460,6128,6129],{},"UITableView","\ncontrols. Android, while staying more on the lower SQLite level, also provides similar integration to their native ",[152,6132,6135],{"href":6133,"rel":6134},"http://developer.android.com/reference/android/widget/SimpleCursorAdapter.html",[156],[460,6136,6137],{},"ListView",[160,6139,6141],{"id":6140},"views-user-interface-ui-elements","Views & user interface (UI) elements",[19,6143,6144,6148],{},[33,6145],{"alt":6146,"src":6147},"\"iPhone UI Controls\"","https://media.synyx.de/uploads//2010/07/iPhone-UI-Controls-e1280510112988.png","\nEvery mobile SDK contains a selection of useful UI elements: buttons, sliders, text entry controls, switches and on a\nlarger scale prebuilt view elements for tabular data, images, display of rich text or maps for geolocative services.\nUsually every of those elements or views exhibits a callback interface tailored towards the particular item. These can\nbecome quite sophisticated: views for tabular data call back for touches on a particular row and/or column or map views\nrequesting to redraw the viewport according to new coordinates after scrolling occured.",[160,6150,6152],{"id":6151},"controllers","Controllers",[19,6154,6155],{},"The backbone of every applications, they are the heavy lifters which glue your models and views together. Often the\ntasks to be done — for example managing the display of overviews and increasingly detailled item views or also an\neditable table view of data from an underlying data source — are abstract and common enough, that a selection of generic\ncontrollers can eliminate boilerplate code and provide a feature rich set of actions out of the box.",[1483,6157,6159],{"id":6158},"javascript-frameworks-the-small-the-huge-and-the-familiar","JavaScript frameworks … the small, the huge and the familiar",[160,6161,6163],{"id":6162},"jqtouch","jQTouch",[19,6165,6166,6167,6171,6172,6177,6178,6185,6186,6188],{},"Our first framework is perhaps the one which contradicts our demands the most. ",[152,6168,6163],{"href":6169,"rel":6170},"http://jqtouch.com",[156]," is a\nlightweight layer on the ever-present ",[152,6173,6176],{"href":6174,"rel":6175},"http://jquery.com",[156],"jQuery"," library. And with lightweight we are talking about a\nmeager 577 source lines of code. Obviously one cannot expect a lot of the desired abstractions, yet still jQTouch has\nits own eligibility. It provides mainly an implementation of one of the most common view abstractions — a stack of menus\nand toolbars to navigate forward and backward — bundled with finely tuned themes for an iPhone-like or more generic\nmobile UI. While you still have to provide the code for more complex controllers and also data model abstraction, it can\nbe used for those cases, where an already deployed web application employing jQuery, should be quickly ported to a\nmobile device. Furthermore it features resource-preloading as also access to geolocation APIs in mobile WebKit\nimplementations. The missing model and controller code can be adapted from existing desktop browser jQuery extensions,\nwhich are numerous. It is now maintained by Jonathan Stark, whose book ",[152,6179,6182],{"href":6180,"rel":6181},"http://building-iphone-apps.labs.oreilly.com/",[156],[693,6183,6184],{},"Building iPhone Apps with HTML, CSS &\nJavaScript"," we already mentioned in part one of this article series and\nwhich is still a great resource for ",[693,6187,6104],{}," development on mobile devices.",[160,6190,6192],{"id":6191},"sencha-touch","Sencha Touch",[19,6194,6195,6200,6201,6205,6206,6211],{},[152,6196,6199],{"href":6197,"rel":6198},"http://www.sencha.com/",[156],"Sencha Labs"," (formerly ExtJS) are already well-known for their desktop browser frameworks.\nWith ",[152,6202,6192],{"href":6203,"rel":6204},"http://www.sencha.com/products/touch/",[156]," they provide abstractions for many of the use cases we talked\nabout above. This includes controllers for tool- or tabbar navigation, data sources which can be attached to analogous\nviews — which then are updated automatically, common entry controls like textfields or date pickers, map views and media\nlike audio and video. Every control provided has an extensive list of event callbacks, that you can easily attach your\nfunctions to. From the ",[152,6207,6210],{"href":6208,"rel":6209},"http://www.sencha.com/products/touch/demos.php",[156],"demos"," it becomes clear that they also push hard\ntowards the emerging tablet devices by providing a full-stack framework for even sophisticated applications. It should\nbe noted though, that it is dual-licensed, with commercial use entailing a (small) fee.",[160,6213,6215],{"id":6214},"jquery-mobile","jQuery mobile",[19,6217,6218,6219,6224,6225,6230],{},"For people, who in the past have worked with ",[152,6220,6223],{"href":6221,"rel":6222},"http://jqueryui.com",[156],"jQuery UI"," for web applications, John Resig recently\nannounced ",[152,6226,6229],{"href":6227,"rel":6228},"http://jquerymobile.com/",[156],"jQuery Mobile",". While it is still in planning & internal beta, it could prove as\nthe missing link from the formerly mentioned jQTouch. Projecting from the existing jQuery UI framework, this would\nprovide a good amount of UI controls and common view abstractions, while maintaining the slick feeling of traditional\njQuery programming. It will also come with themes, which match typical native mobile controls and a strong mission\nstatement to make it a real cross-platform alternative, beyond the iOS platform usually targeted by other frameworks.",[160,6232,6234],{"id":6233},"google-web-toolkit","Google Web Toolkit",[19,6236,6237,6238,6242,6243,6248,6249,6254,6255,6260,6261,6266],{},"Last but definitely not least, we have to include an old familiar\nfriend: ",[152,6239,6234],{"href":6240,"rel":6241},"http://code.google.com/webtoolkit/",[156]," (GWT) has already matured for desktop browsers. It also\nhas something to provide, which other JavaScript frameworks lack: extensive IDE support with a strictly typed language\nunderneath. Especially Android developers will feel more at home, when developing with Java. While originally targeted\nat desktop browsers, its extensibility makes it easy to retarget mobile\nplatforms. ",[152,6244,6247],{"href":6245,"rel":6246},"http://code.google.com/p/gwt-mobile-webkit/",[156],"GWT Mobile WebKit"," extends GWT with support for touch\ninterfaces and also bundles libraries to\nabstract ",[152,6250,6253],{"href":6251,"rel":6252},"http://code.google.com/p/gwt-mobile-webkit/downloads/list?q=label:API-Geolocation",[156],"geolocation services"," and\nthe ",[152,6256,6259],{"href":6257,"rel":6258},"http://code.google.com/p/gwt-mobile-webkit/downloads/list?q=label:API-Database",[156],"SQLite"," database — something even\nmissing from the original Android SDK. Views can be created programmatically\nor ",[152,6262,6265],{"href":6263,"rel":6264},"http://code.google.com/webtoolkit/doc/latest/DevGuideUiBinder.html",[156],"declaratively"," and provide a rich API for\ncallbacks on user input.",[23,6268,6270],{"id":6269},"what-is-left-to-be-said","What is left to be said",[19,6272,6273,6274,6276,6277,6279,6280,6283],{},"The ",[693,6275,6104],{}," mobile development community is certainly on the move to sophisticated frameworks. From small\nmicroframeworks to full-stack frameworks like ",[693,6278,6192],{}," or ",[693,6281,6282],{},"GWT",", a range of tastes for different development\nstyles is served. While we tried to give a bigger perspective on what is available, we still haven’t even touched topics\nlike the upcoming HTML5 — which together with CSS3 will bring fast animations and sophisticated graphics — or actual\ncross-device behaviour. What can be definitely said is, that these developments — wether or not one decides to jump on\nthis bandwagon — increase the diversity of mobile development and provide new insights in how we think about mobile\napplication development.",[19,6285,6286],{},"To be continued some time …",{"title":110,"searchDepth":111,"depth":111,"links":6288},[6289,6290,6293],{"id":6050,"depth":111,"text":6051},{"id":6077,"depth":111,"text":6078,"children":6291},[6292],{"id":6158,"depth":671,"text":6159},{"id":6269,"depth":111,"text":6270},[120],"2010-09-07T12:00:39","In the previous part of this series we took\\na look on how to develop mobile applications with plain HTML, CSS & JavaScript (furthermore refered to as the web\\nstack). A few pieces of absolutely laid out CSS for the views, a dash of custom jQuery events for controller code\\ninvocation and standard in-browser SQLite access for the model — every aspect of a MVC application should be accounted\\nfor, shouldn’t it? Not quite …","https://synyx.de/blog/on-cross-device-mobile-development-part-2/",{},"/blog/on-cross-device-mobile-development-part-2",{"title":6028,"description":6301},"In the previous part of this series we took\na look on how to develop mobile applications with plain HTML, CSS & JavaScript (furthermore refered to as the web\nstack). A few pieces of absolutely laid out CSS for the views, a dash of custom jQuery events for controller code\ninvocation and standard in-browser SQLite access for the model — every aspect of a MVC application should be accounted\nfor, shouldn’t it? Not quite …","blog/on-cross-device-mobile-development-part-2",[133,6304,789,5047,4778],"css","In the previous part of this series we took a look on how to develop mobile applications with plain HTML, CSS & JavaScript (furthermore refered to as the web stack). A…","YieTuihm_5oZzpoNHWVn3JDuz9uBqr4EfoBq26hjIl0",{"id":6308,"title":6309,"author":6310,"body":6311,"category":6405,"date":6406,"description":6407,"extension":124,"link":6408,"meta":6409,"navigation":127,"path":6410,"seo":6411,"slug":6315,"stem":6413,"tags":6414,"teaser":6415,"__hash__":6416},"blog/blog/testing-apps-with-in-app-purchases-in-simulator.md","Testing Apps with In App Purchases in Simulator",[5023],{"type":12,"value":6312,"toc":6403},[6313,6316,6331,6337,6346,6398,6401],[15,6314,6309],{"id":6315},"testing-apps-with-in-app-purchases-in-simulator",[19,6317,6318,6319,6324,6325,6330],{},"If you add a store to your app and\nuse ",[152,6320,6323],{"href":6321,"rel":6322},"http://developer.apple.com/iphone/library/documentation/NetworkingInternet/Conceptual/StoreKitGuide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40008267-CH1-SW1",[156],"In App Purchases","\nto collect your payments, there are a couple of limitations your have to live with. One of those limitations\nis ",[152,6326,6329],{"href":6327,"rel":6328},"http://developer.apple.com/iphone/library/documentation/NetworkingInternet/Conceptual/StoreKitGuide/DevelopingwithStoreKit/DevelopingwithStoreKit.html#//apple_ref/doc/uid/TP40008267-CH103-SW1",[156],"not being able to fully test your App in the iPhone Simulator",":",[6332,6333,6334],"blockquote",{},[19,6335,6336],{},"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.",[19,6338,6339,6340,6345],{},"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 ",[152,6341,6344],{"href":6342,"rel":6343},"http://en.wikipedia.org/wiki/C_preprocessor#Macro_definition_and_expansion",[156],"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.",[649,6347,6351],{"className":6348,"code":6349,"language":6350,"meta":110,"style":110},"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",[460,6352,6353,6358,6363,6368,6373,6378,6383,6388,6393],{"__ignoreMap":110},[657,6354,6355],{"class":659,"line":660},[657,6356,6357],{},"#if TARGET_IPHONE_SIMULATOR\n",[657,6359,6360],{"class":659,"line":111},[657,6361,6362],{},"// mock product description\n",[657,6364,6365],{"class":659,"line":671},[657,6366,6367],{},"#else\n",[657,6369,6370],{"class":659,"line":677},[657,6371,6372],{},"SKProductsRequest *productRequest =\n",[657,6374,6375],{"class":659,"line":683},[657,6376,6377],{},"[[SKProductsRequest alloc] initWithProductIdentifiers:productIds];\n",[657,6379,6380],{"class":659,"line":732},[657,6381,6382],{},"productRequest.delegate = self;\n",[657,6384,6385],{"class":659,"line":738},[657,6386,6387],{},"[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;\n",[657,6389,6390],{"class":659,"line":744},[657,6391,6392],{},"[productRequest start];\n",[657,6394,6395],{"class":659,"line":750},[657,6396,6397],{},"#endif\n",[19,6399,6400],{},"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.",[1341,6402,2074],{},{"title":110,"searchDepth":111,"depth":111,"links":6404},[],[120,483],"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":6309,"description":6412},"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",[5046,5047,5342],"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":6418,"title":6419,"author":6420,"body":6421,"category":6543,"date":6544,"description":110,"extension":124,"link":6545,"meta":6546,"navigation":127,"path":6547,"seo":6548,"slug":6425,"stem":6549,"tags":6550,"teaser":6551,"__hash__":6552},"blog/blog/settings-bundle-and-default-values.md","Settings Bundle and Default Values",[5023],{"type":12,"value":6422,"toc":6541},[6423,6426,6430,6479,6492,6525,6536,6539],[15,6424,6419],{"id":6425},"settings-bundle-and-default-values",[19,6427,6428],{},[33,6429],{"alt":5033,"src":5871},[649,6431,6433],{"className":651,"code":6432,"language":653,"meta":110,"style":110},"\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",[460,6434,6435,6439,6444,6449,6454,6459,6464,6469,6474],{"__ignoreMap":110},[657,6436,6437],{"class":659,"line":660},[657,6438,663],{"emptyLinePlaceholder":127},[657,6440,6441],{"class":659,"line":111},[657,6442,6443],{},"\u003Ckey>Type\u003C/key>\n",[657,6445,6446],{"class":659,"line":671},[657,6447,6448],{},"\u003Cstring>PSToggleSwitchSpecifier\u003C/string>\n",[657,6450,6451],{"class":659,"line":677},[657,6452,6453],{},"\u003Ckey>Title\u003C/key>\n",[657,6455,6456],{"class":659,"line":683},[657,6457,6458],{},"\u003Cstring>Sound\u003C/string>\n",[657,6460,6461],{"class":659,"line":732},[657,6462,6463],{},"\u003Ckey>Key\u003C/key>\n",[657,6465,6466],{"class":659,"line":738},[657,6467,6468],{},"\u003Cstring>sound_enabled\u003C/string>\n",[657,6470,6471],{"class":659,"line":744},[657,6472,6473],{},"\u003Ckey>DefaultValue\u003C/key>\n",[657,6475,6476],{"class":659,"line":750},[657,6477,6478],{},"\u003Ctrue/>\n",[19,6480,6481,6482,6487,6488,6491],{},"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 ",[152,6483,6486],{"href":6484,"rel":6485},"http://stackoverflow.com/questions/510216/can-you-make-the-settings-in-settings-bundle-default-even-if-you-dont-open-the-s/510329#510329",[156],"found a workaround",",\nwhich we implemented in our AppDelegate’s ",[693,6489,6490],{},"didFinishLaunchingWithOptions"," method:",[649,6493,6495],{"className":6348,"code":6494,"language":6350,"meta":110,"style":110},"\nid test = [[NSUserDefaults standardUserDefaults] objectForKey:@\"sound_enabled\"];\nif (test == NULL) {\n [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@\"sound_enabled\"];\n}\nreturn YES;\n",[460,6496,6497,6501,6506,6511,6516,6520],{"__ignoreMap":110},[657,6498,6499],{"class":659,"line":660},[657,6500,663],{"emptyLinePlaceholder":127},[657,6502,6503],{"class":659,"line":111},[657,6504,6505],{},"id test = [[NSUserDefaults standardUserDefaults] objectForKey:@\"sound_enabled\"];\n",[657,6507,6508],{"class":659,"line":671},[657,6509,6510],{},"if (test == NULL) {\n",[657,6512,6513],{"class":659,"line":677},[657,6514,6515],{}," [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@\"sound_enabled\"];\n",[657,6517,6518],{"class":659,"line":683},[657,6519,2438],{},[657,6521,6522],{"class":659,"line":732},[657,6523,6524],{},"return YES;\n",[19,6526,6527,6528,6531,6532,6535],{},"This checks if no value was set, which means the returned value is ",[693,6529,6530],{},"NULL",", and in that case sets our default value. Note\nthat we are duplicating the default value definition here. You could also access the ",[693,6533,6534],{},"Settings Bundle"," programmatically\nand read the default value from there.",[19,6537,6538],{},"This gives us an easy workaround, so that you can enjoy the awesome sound effects in “I think I spider”.",[1341,6540,2074],{},{"title":110,"searchDepth":111,"depth":111,"links":6542},[],[120,263,483],"2010-08-16T06:38:08","https://synyx.de/blog/settings-bundle-and-default-values/",{},"/blog/settings-bundle-and-default-values",{"title":6419,"description":110},"blog/settings-bundle-and-default-values",[5046,5047],"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":6554,"title":6555,"author":6556,"body":6557,"category":8103,"date":8104,"description":8105,"extension":124,"link":8106,"meta":8107,"navigation":127,"path":8108,"seo":8109,"slug":6561,"stem":8111,"tags":8112,"teaser":8113,"__hash__":8114},"blog/blog/on-cross-device-mobile-development-part-1.md","On cross-device mobile development – Part 1",[4962],{"type":12,"value":6558,"toc":8097},[6559,6562,6569,6573,6576,6580,6588,6892,6895,7610,7613,7816,7827,7877,7880,8047,8051,8060,8063,8070,8074,8077,8091,8094],[15,6560,6555],{"id":6561},"on-cross-device-mobile-development-part-1",[19,6563,6564,6565,6568],{},"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. ",[693,6566,6567],{},"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.",[23,6570,6572],{"id":6571},"its-not-in-the-browser","It’s (not) in the browser",[19,6574,6575],{},"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.",[23,6577,6579],{"id":6578},"of-models-views-controllers","Of models, views & controllers",[19,6581,625,6582,6587],{},[152,6583,6586],{"href":6584,"rel":6585},"http://building-iphone-apps.labs.oreilly.com/ch04.html",[156],"“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:",[649,6589,6591],{"className":787,"code":6590,"language":789,"meta":110,"style":110},"\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",[460,6592,6593,6608,6646,6674,6702,6730,6758,6766,6792,6817,6842,6867],{"__ignoreMap":110},[657,6594,6595,6597,6599,6601,6603,6606],{"class":659,"line":660},[657,6596,797],{"class":796},[657,6598,85],{"class":2834},[657,6600,3154],{"class":2838},[657,6602,2872],{"class":796},[657,6604,6605],{"class":2875},"\"navigation\"",[657,6607,804],{"class":796},[657,6609,6610,6612,6614,6617,6619,6621,6623,6626,6629,6631,6634,6637,6639,6642,6644],{"class":659,"line":111},[657,6611,809],{"class":796},[657,6613,88],{"class":2834},[657,6615,6616],{"class":796},">\u003C",[657,6618,152],{"class":2834},[657,6620,3011],{"class":2838},[657,6622,2872],{"class":796},[657,6624,6625],{"class":2875},"\"current\"",[657,6627,6628],{"class":2838}," href",[657,6630,2872],{"class":796},[657,6632,6633],{"class":2875},"\"#home\"",[657,6635,6636],{"class":796},">Home\u003C/",[657,6638,152],{"class":2834},[657,6640,6641],{"class":796},">\u003C/",[657,6643,88],{"class":2834},[657,6645,804],{"class":796},[657,6647,6648,6650,6652,6654,6656,6658,6660,6663,6666,6668,6670,6672],{"class":659,"line":671},[657,6649,809],{"class":796},[657,6651,88],{"class":2834},[657,6653,6616],{"class":796},[657,6655,152],{"class":2834},[657,6657,6628],{"class":2838},[657,6659,2872],{"class":796},[657,6661,6662],{"class":2875},"\"#new\"",[657,6664,6665],{"class":796},">New things\u003C/",[657,6667,152],{"class":2834},[657,6669,6641],{"class":796},[657,6671,88],{"class":2834},[657,6673,804],{"class":796},[657,6675,6676,6678,6680,6682,6684,6686,6688,6691,6694,6696,6698,6700],{"class":659,"line":677},[657,6677,809],{"class":796},[657,6679,88],{"class":2834},[657,6681,6616],{"class":796},[657,6683,152],{"class":2834},[657,6685,6628],{"class":2838},[657,6687,2872],{"class":796},[657,6689,6690],{"class":2875},"\"#favorites\"",[657,6692,6693],{"class":796},">I like those\u003C/",[657,6695,152],{"class":2834},[657,6697,6641],{"class":796},[657,6699,88],{"class":2834},[657,6701,804],{"class":796},[657,6703,6704,6706,6708,6710,6712,6714,6716,6719,6722,6724,6726,6728],{"class":659,"line":683},[657,6705,809],{"class":796},[657,6707,88],{"class":2834},[657,6709,6616],{"class":796},[657,6711,152],{"class":2834},[657,6713,6628],{"class":2838},[657,6715,2872],{"class":796},[657,6717,6718],{"class":2875},"\"#more\"",[657,6720,6721],{"class":796},">More content\u003C/",[657,6723,152],{"class":2834},[657,6725,6641],{"class":796},[657,6727,88],{"class":2834},[657,6729,804],{"class":796},[657,6731,6732,6734,6736,6738,6740,6742,6744,6747,6750,6752,6754,6756],{"class":659,"line":732},[657,6733,809],{"class":796},[657,6735,88],{"class":2834},[657,6737,6616],{"class":796},[657,6739,152],{"class":2834},[657,6741,6628],{"class":2838},[657,6743,2872],{"class":796},[657,6745,6746],{"class":2875},"\"#info\"",[657,6748,6749],{"class":796},">About\u003C/",[657,6751,152],{"class":2834},[657,6753,6641],{"class":796},[657,6755,88],{"class":2834},[657,6757,804],{"class":796},[657,6759,6760,6762,6764],{"class":659,"line":738},[657,6761,843],{"class":796},[657,6763,85],{"class":2834},[657,6765,804],{"class":796},[657,6767,6768,6770,6772,6774,6776,6779,6781,6783,6786,6788,6790],{"class":659,"line":744},[657,6769,797],{"class":796},[657,6771,3008],{"class":2834},[657,6773,3011],{"class":2838},[657,6775,2872],{"class":796},[657,6777,6778],{"class":2875},"\"view\"",[657,6780,3154],{"class":2838},[657,6782,2872],{"class":796},[657,6784,6785],{"class":2875},"\"home\"",[657,6787,4735],{"class":796},[657,6789,3008],{"class":2834},[657,6791,804],{"class":796},[657,6793,6794,6796,6798,6800,6802,6804,6806,6808,6811,6813,6815],{"class":659,"line":750},[657,6795,797],{"class":796},[657,6797,3008],{"class":2834},[657,6799,3011],{"class":2838},[657,6801,2872],{"class":796},[657,6803,6778],{"class":2875},[657,6805,3154],{"class":2838},[657,6807,2872],{"class":796},[657,6809,6810],{"class":2875},"\"new\"",[657,6812,4735],{"class":796},[657,6814,3008],{"class":2834},[657,6816,804],{"class":796},[657,6818,6819,6821,6823,6825,6827,6829,6831,6833,6836,6838,6840],{"class":659,"line":756},[657,6820,797],{"class":796},[657,6822,3008],{"class":2834},[657,6824,3011],{"class":2838},[657,6826,2872],{"class":796},[657,6828,6778],{"class":2875},[657,6830,3154],{"class":2838},[657,6832,2872],{"class":796},[657,6834,6835],{"class":2875},"\"favorites\"",[657,6837,4735],{"class":796},[657,6839,3008],{"class":2834},[657,6841,804],{"class":796},[657,6843,6844,6846,6848,6850,6852,6854,6856,6858,6861,6863,6865],{"class":659,"line":907},[657,6845,797],{"class":796},[657,6847,3008],{"class":2834},[657,6849,3011],{"class":2838},[657,6851,2872],{"class":796},[657,6853,6778],{"class":2875},[657,6855,3154],{"class":2838},[657,6857,2872],{"class":796},[657,6859,6860],{"class":2875},"\"more\"",[657,6862,4735],{"class":796},[657,6864,3008],{"class":2834},[657,6866,804],{"class":796},[657,6868,6869,6871,6873,6875,6877,6879,6881,6883,6886,6888,6890],{"class":659,"line":913},[657,6870,797],{"class":796},[657,6872,3008],{"class":2834},[657,6874,3011],{"class":2838},[657,6876,2872],{"class":796},[657,6878,6778],{"class":2875},[657,6880,3154],{"class":2838},[657,6882,2872],{"class":796},[657,6884,6885],{"class":2875},"\"info\"",[657,6887,4735],{"class":796},[657,6889,3008],{"class":2834},[657,6891,804],{"class":796},[19,6893,6894],{},"The navigational items are then replaced with nice button graphics via CSS and positioned on the page.",[649,6896,6899],{"className":6897,"code":6898,"language":6304,"meta":110,"style":110},"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",[460,6900,6901,6906,6913,6926,6941,6954,6968,6979,6990,6994,7003,7015,7026,7030,7041,7051,7061,7075,7088,7100,7114,7118,7135,7152,7164,7168,7188,7203,7207,7221,7236,7248,7252,7270,7285,7289,7303,7318,7331,7335,7353,7368,7373,7388,7404,7418,7423,7438,7454,7465,7478,7491,7506,7520,7525,7531,7541,7552,7565,7578,7591,7605],{"__ignoreMap":110},[657,6902,6903],{"class":659,"line":660},[657,6904,6905],{"class":5105},"/* navigation is a fixed block */\n",[657,6907,6908,6911],{"class":659,"line":111},[657,6909,6910],{"class":2838},"#navigation",[657,6912,5276],{"class":796},[657,6914,6915,6918,6921,6924],{"class":659,"line":671},[657,6916,6917],{"class":4865}," position",[657,6919,6920],{"class":796},": ",[657,6922,6923],{"class":4865},"fixed",[657,6925,5180],{"class":796},[657,6927,6928,6931,6933,6936,6939],{"class":659,"line":677},[657,6929,6930],{"class":4865}," top",[657,6932,6920],{"class":796},[657,6934,6935],{"class":4865},"0",[657,6937,6938],{"class":4785},"px",[657,6940,5180],{"class":796},[657,6942,6943,6946,6948,6950,6952],{"class":659,"line":683},[657,6944,6945],{"class":4865}," left",[657,6947,6920],{"class":796},[657,6949,6935],{"class":4865},[657,6951,6938],{"class":4785},[657,6953,5180],{"class":796},[657,6955,6956,6959,6961,6964,6966],{"class":659,"line":732},[657,6957,6958],{"class":4865}," width",[657,6960,6920],{"class":796},[657,6962,6963],{"class":4865},"320",[657,6965,6938],{"class":4785},[657,6967,5180],{"class":796},[657,6969,6970,6973,6975,6977],{"class":659,"line":738},[657,6971,6972],{"class":4865}," margin",[657,6974,6920],{"class":796},[657,6976,6935],{"class":4865},[657,6978,5180],{"class":796},[657,6980,6981,6984,6986,6988],{"class":659,"line":744},[657,6982,6983],{"class":4865}," padding",[657,6985,6920],{"class":796},[657,6987,6935],{"class":4865},[657,6989,5180],{"class":796},[657,6991,6992],{"class":659,"line":750},[657,6993,2438],{"class":796},[657,6995,6996,6998,7001],{"class":659,"line":756},[657,6997,6910],{"class":2838},[657,6999,7000],{"class":2834}," li",[657,7002,5276],{"class":796},[657,7004,7005,7008,7010,7013],{"class":659,"line":907},[657,7006,7007],{"class":4865}," display",[657,7009,6920],{"class":796},[657,7011,7012],{"class":4865},"block",[657,7014,5180],{"class":796},[657,7016,7017,7019,7021,7024],{"class":659,"line":913},[657,7018,6917],{"class":4865},[657,7020,6920],{"class":796},[657,7022,7023],{"class":4865},"absolute",[657,7025,5180],{"class":796},[657,7027,7028],{"class":659,"line":919},[657,7029,2438],{"class":796},[657,7031,7032,7034,7036,7039],{"class":659,"line":1006},[657,7033,6910],{"class":2838},[657,7035,7000],{"class":2834},[657,7037,7038],{"class":2834}," a",[657,7040,5276],{"class":796},[657,7042,7043,7045,7047,7049],{"class":659,"line":1088},[657,7044,7007],{"class":4865},[657,7046,6920],{"class":796},[657,7048,7012],{"class":4865},[657,7050,5180],{"class":796},[657,7052,7053,7055,7057,7059],{"class":659,"line":1094},[657,7054,6917],{"class":4865},[657,7056,6920],{"class":796},[657,7058,7023],{"class":4865},[657,7060,5180],{"class":796},[657,7062,7063,7066,7068,7071,7073],{"class":659,"line":1100},[657,7064,7065],{"class":4865}," height",[657,7067,6920],{"class":796},[657,7069,7070],{"class":4865},"48",[657,7072,6938],{"class":4785},[657,7074,5180],{"class":796},[657,7076,7077,7079,7081,7084,7086],{"class":659,"line":1106},[657,7078,6958],{"class":4865},[657,7080,6920],{"class":796},[657,7082,7083],{"class":4865},"80",[657,7085,6938],{"class":4785},[657,7087,5180],{"class":796},[657,7089,7090,7092,7094,7096,7098],{"class":659,"line":1112},[657,7091,6930],{"class":4865},[657,7093,6920],{"class":796},[657,7095,6935],{"class":4865},[657,7097,6938],{"class":4785},[657,7099,5180],{"class":796},[657,7101,7102,7105,7107,7110,7112],{"class":659,"line":1118},[657,7103,7104],{"class":4865}," text-indent",[657,7106,6920],{"class":796},[657,7108,7109],{"class":4865},"-9999",[657,7111,6938],{"class":4785},[657,7113,5180],{"class":796},[657,7115,7116],{"class":659,"line":1124},[657,7117,2438],{"class":796},[657,7119,7120,7122,7125,7128,7130,7132],{"class":659,"line":1130},[657,7121,152],{"class":2834},[657,7123,7124],{"class":796},"[",[657,7126,7127],{"class":2838},"href",[657,7129,2872],{"class":4785},[657,7131,6633],{"class":2875},[657,7133,7134],{"class":796},"] {\n",[657,7136,7137,7140,7142,7145,7147,7150],{"class":659,"line":1136},[657,7138,7139],{"class":4865}," background",[657,7141,6920],{"class":796},[657,7143,7144],{"class":4865},"url",[657,7146,4792],{"class":796},[657,7148,7149],{"class":4795},"home.png",[657,7151,5206],{"class":796},[657,7153,7154,7156,7158,7160,7162],{"class":659,"line":1142},[657,7155,6945],{"class":4865},[657,7157,6920],{"class":796},[657,7159,6935],{"class":4865},[657,7161,6938],{"class":4785},[657,7163,5180],{"class":796},[657,7165,7166],{"class":659,"line":1148},[657,7167,2438],{"class":796},[657,7169,7170,7172,7174,7176,7178,7180,7183,7186],{"class":659,"line":1154},[657,7171,152],{"class":2834},[657,7173,7124],{"class":796},[657,7175,7127],{"class":2838},[657,7177,2872],{"class":4785},[657,7179,6633],{"class":2875},[657,7181,7182],{"class":796},"]",[657,7184,7185],{"class":2838},".current",[657,7187,5276],{"class":796},[657,7189,7190,7192,7194,7196,7198,7201],{"class":659,"line":1160},[657,7191,7139],{"class":4865},[657,7193,6920],{"class":796},[657,7195,7144],{"class":4865},[657,7197,4792],{"class":796},[657,7199,7200],{"class":4795},"home-current.png",[657,7202,5206],{"class":796},[657,7204,7205],{"class":659,"line":1166},[657,7206,2438],{"class":796},[657,7208,7209,7211,7213,7215,7217,7219],{"class":659,"line":1172},[657,7210,152],{"class":2834},[657,7212,7124],{"class":796},[657,7214,7127],{"class":2838},[657,7216,2872],{"class":4785},[657,7218,6662],{"class":2875},[657,7220,7134],{"class":796},[657,7222,7223,7225,7227,7229,7231,7234],{"class":659,"line":1178},[657,7224,7139],{"class":4865},[657,7226,6920],{"class":796},[657,7228,7144],{"class":4865},[657,7230,4792],{"class":796},[657,7232,7233],{"class":4795},"new.png",[657,7235,5206],{"class":796},[657,7237,7238,7240,7242,7244,7246],{"class":659,"line":1183},[657,7239,6945],{"class":4865},[657,7241,6920],{"class":796},[657,7243,7083],{"class":4865},[657,7245,6938],{"class":4785},[657,7247,5180],{"class":796},[657,7249,7250],{"class":659,"line":1188},[657,7251,2438],{"class":796},[657,7253,7254,7256,7258,7260,7262,7264,7266,7268],{"class":659,"line":1194},[657,7255,152],{"class":2834},[657,7257,7124],{"class":796},[657,7259,7127],{"class":2838},[657,7261,2872],{"class":4785},[657,7263,6662],{"class":2875},[657,7265,7182],{"class":796},[657,7267,7185],{"class":2838},[657,7269,5276],{"class":796},[657,7271,7272,7274,7276,7278,7280,7283],{"class":659,"line":1199},[657,7273,7139],{"class":4865},[657,7275,6920],{"class":796},[657,7277,7144],{"class":4865},[657,7279,4792],{"class":796},[657,7281,7282],{"class":4795},"new-current.png",[657,7284,5206],{"class":796},[657,7286,7287],{"class":659,"line":1205},[657,7288,2438],{"class":796},[657,7290,7291,7293,7295,7297,7299,7301],{"class":659,"line":1210},[657,7292,152],{"class":2834},[657,7294,7124],{"class":796},[657,7296,7127],{"class":2838},[657,7298,2872],{"class":4785},[657,7300,6690],{"class":2875},[657,7302,7134],{"class":796},[657,7304,7305,7307,7309,7311,7313,7316],{"class":659,"line":1215},[657,7306,7139],{"class":4865},[657,7308,6920],{"class":796},[657,7310,7144],{"class":4865},[657,7312,4792],{"class":796},[657,7314,7315],{"class":4795},"favorites.png",[657,7317,5206],{"class":796},[657,7319,7320,7322,7324,7327,7329],{"class":659,"line":1220},[657,7321,6945],{"class":4865},[657,7323,6920],{"class":796},[657,7325,7326],{"class":4865},"160",[657,7328,6938],{"class":4785},[657,7330,5180],{"class":796},[657,7332,7333],{"class":659,"line":1225},[657,7334,2438],{"class":796},[657,7336,7337,7339,7341,7343,7345,7347,7349,7351],{"class":659,"line":1230},[657,7338,152],{"class":2834},[657,7340,7124],{"class":796},[657,7342,7127],{"class":2838},[657,7344,2872],{"class":4785},[657,7346,6690],{"class":2875},[657,7348,7182],{"class":796},[657,7350,7185],{"class":2838},[657,7352,5276],{"class":796},[657,7354,7355,7357,7359,7361,7363,7366],{"class":659,"line":1236},[657,7356,7139],{"class":4865},[657,7358,6920],{"class":796},[657,7360,7144],{"class":4865},[657,7362,4792],{"class":796},[657,7364,7365],{"class":4795},"favorites-current.png",[657,7367,5206],{"class":796},[657,7369,7371],{"class":659,"line":7370},42,[657,7372,2438],{"class":796},[657,7374,7376,7378,7380,7382,7384,7386],{"class":659,"line":7375},43,[657,7377,152],{"class":2834},[657,7379,7124],{"class":796},[657,7381,7127],{"class":2838},[657,7383,2872],{"class":4785},[657,7385,6718],{"class":2875},[657,7387,7134],{"class":796},[657,7389,7391,7393,7395,7397,7399,7402],{"class":659,"line":7390},44,[657,7392,7139],{"class":4865},[657,7394,6920],{"class":796},[657,7396,7144],{"class":4865},[657,7398,4792],{"class":796},[657,7400,7401],{"class":4795},"more.png",[657,7403,5206],{"class":796},[657,7405,7407,7409,7411,7414,7416],{"class":659,"line":7406},45,[657,7408,6945],{"class":4865},[657,7410,6920],{"class":796},[657,7412,7413],{"class":4865},"240",[657,7415,6938],{"class":4785},[657,7417,5180],{"class":796},[657,7419,7421],{"class":659,"line":7420},46,[657,7422,2438],{"class":796},[657,7424,7426,7428,7430,7432,7434,7436],{"class":659,"line":7425},47,[657,7427,152],{"class":2834},[657,7429,7124],{"class":796},[657,7431,7127],{"class":2838},[657,7433,2872],{"class":4785},[657,7435,6746],{"class":2875},[657,7437,7134],{"class":796},[657,7439,7441,7443,7445,7447,7449,7452],{"class":659,"line":7440},48,[657,7442,7139],{"class":4865},[657,7444,6920],{"class":796},[657,7446,7144],{"class":4865},[657,7448,4792],{"class":796},[657,7450,7451],{"class":4795},"info.png",[657,7453,5206],{"class":796},[657,7455,7457,7459,7461,7463],{"class":659,"line":7456},49,[657,7458,6917],{"class":4865},[657,7460,6920],{"class":796},[657,7462,6923],{"class":4865},[657,7464,5180],{"class":796},[657,7466,7468,7470,7472,7474,7476],{"class":659,"line":7467},50,[657,7469,6958],{"class":4865},[657,7471,6920],{"class":796},[657,7473,7070],{"class":4865},[657,7475,6938],{"class":4785},[657,7477,5180],{"class":796},[657,7479,7481,7483,7485,7487,7489],{"class":659,"line":7480},51,[657,7482,7065],{"class":4865},[657,7484,6920],{"class":796},[657,7486,7070],{"class":4865},[657,7488,6938],{"class":4785},[657,7490,5180],{"class":796},[657,7492,7494,7497,7499,7502,7504],{"class":659,"line":7493},52,[657,7495,7496],{"class":4865}," bottom",[657,7498,6920],{"class":796},[657,7500,7501],{"class":4865},"16",[657,7503,6938],{"class":4785},[657,7505,5180],{"class":796},[657,7507,7509,7512,7514,7516,7518],{"class":659,"line":7508},53,[657,7510,7511],{"class":4865}," right",[657,7513,6920],{"class":796},[657,7515,7501],{"class":4865},[657,7517,6938],{"class":4785},[657,7519,5180],{"class":796},[657,7521,7523],{"class":659,"line":7522},54,[657,7524,2438],{"class":796},[657,7526,7528],{"class":659,"line":7527},55,[657,7529,7530],{"class":5105},"/* finally position the views themselves */\n",[657,7532,7534,7536,7539],{"class":659,"line":7533},56,[657,7535,3008],{"class":2834},[657,7537,7538],{"class":2838},".view",[657,7540,5276],{"class":796},[657,7542,7544,7546,7548,7550],{"class":659,"line":7543},57,[657,7545,6917],{"class":4865},[657,7547,6920],{"class":796},[657,7549,7023],{"class":4865},[657,7551,5180],{"class":796},[657,7553,7555,7557,7559,7561,7563],{"class":659,"line":7554},58,[657,7556,6930],{"class":4865},[657,7558,6920],{"class":796},[657,7560,7070],{"class":4865},[657,7562,6938],{"class":4785},[657,7564,5180],{"class":796},[657,7566,7568,7570,7572,7574,7576],{"class":659,"line":7567},59,[657,7569,6945],{"class":4865},[657,7571,6920],{"class":796},[657,7573,6935],{"class":4865},[657,7575,6938],{"class":4785},[657,7577,5180],{"class":796},[657,7579,7581,7583,7585,7587,7589],{"class":659,"line":7580},60,[657,7582,6958],{"class":4865},[657,7584,6920],{"class":796},[657,7586,6963],{"class":4865},[657,7588,6938],{"class":4785},[657,7590,5180],{"class":796},[657,7592,7594,7596,7598,7601,7603],{"class":659,"line":7593},61,[657,7595,7065],{"class":4865},[657,7597,6920],{"class":796},[657,7599,7600],{"class":4865},"396",[657,7602,6938],{"class":4785},[657,7604,5180],{"class":796},[657,7606,7608],{"class":659,"line":7607},62,[657,7609,2438],{"class":796},[19,7611,7612],{},"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:",[649,7614,7616],{"className":4776,"code":7615,"language":4778,"meta":110,"style":110},"$(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",[460,7617,7618,7636,7656,7670,7684,7722,7727,7732,7748,7769,7774,7788,7811],{"__ignoreMap":110},[657,7619,7620,7623,7626,7629,7631,7633],{"class":659,"line":660},[657,7621,7622],{"class":2838},"$",[657,7624,7625],{"class":796},"(document).",[657,7627,7628],{"class":2838},"ready",[657,7630,4792],{"class":796},[657,7632,4786],{"class":4785},[657,7634,7635],{"class":796}," () {\n",[657,7637,7638,7641,7644,7646,7649,7651,7654],{"class":659,"line":111},[657,7639,7640],{"class":4785}," var",[657,7642,7643],{"class":796}," navitems ",[657,7645,2872],{"class":4785},[657,7647,7648],{"class":2838}," $",[657,7650,4792],{"class":796},[657,7652,7653],{"class":2875},"\"#navigation li a\"",[657,7655,5206],{"class":796},[657,7657,7658,7661,7664,7666,7668],{"class":659,"line":671},[657,7659,7660],{"class":796}," navitems.",[657,7662,7663],{"class":2838},"click",[657,7665,4792],{"class":796},[657,7667,4786],{"class":4785},[657,7669,7635],{"class":796},[657,7671,7672,7675,7678,7680,7682],{"class":659,"line":677},[657,7673,7674],{"class":796}," navitems.",[657,7676,7677],{"class":2838},"removeClass",[657,7679,4792],{"class":796},[657,7681,6625],{"class":2875},[657,7683,5206],{"class":796},[657,7685,7686,7689,7692,7694,7696,7698,7701,7703,7706,7708,7710,7712,7715,7717,7720],{"class":659,"line":683},[657,7687,7688],{"class":4785}," var",[657,7690,7691],{"class":796}," ref ",[657,7693,2872],{"class":4785},[657,7695,7648],{"class":2838},[657,7697,4792],{"class":796},[657,7699,7700],{"class":4865},"this",[657,7702,175],{"class":796},[657,7704,7705],{"class":2838},"addClass",[657,7707,4792],{"class":796},[657,7709,6625],{"class":2875},[657,7711,175],{"class":796},[657,7713,7714],{"class":2838},"attr",[657,7716,4792],{"class":796},[657,7718,7719],{"class":2875},"\"href\"",[657,7721,5206],{"class":796},[657,7723,7724],{"class":659,"line":732},[657,7725,7726],{"class":5105}," /* hide the other views, show the one navigated to\n",[657,7728,7729],{"class":659,"line":738},[657,7730,7731],{"class":5105}," and trigger a custom event */\n",[657,7733,7734,7737,7739,7742,7744,7746],{"class":659,"line":744},[657,7735,7736],{"class":2838}," $",[657,7738,4792],{"class":796},[657,7740,7741],{"class":2875},"\"div.view\"",[657,7743,175],{"class":796},[657,7745,4834],{"class":2838},[657,7747,5125],{"class":796},[657,7749,7750,7752,7755,7757,7759,7762,7764,7767],{"class":659,"line":750},[657,7751,7736],{"class":2838},[657,7753,7754],{"class":796},"(ref).",[657,7756,4889],{"class":2838},[657,7758,5143],{"class":796},[657,7760,7761],{"class":2838},"trigger",[657,7763,4792],{"class":796},[657,7765,7766],{"class":2875},"\"becameActive\"",[657,7768,5206],{"class":796},[657,7770,7771],{"class":659,"line":756},[657,7772,7773],{"class":796}," });\n",[657,7775,7776,7778,7780,7782,7784,7786],{"class":659,"line":907},[657,7777,4820],{"class":2838},[657,7779,4792],{"class":796},[657,7781,7741],{"class":2875},[657,7783,175],{"class":796},[657,7785,4834],{"class":2838},[657,7787,5125],{"class":796},[657,7789,7790,7792,7794,7797,7799,7801,7803,7805,7807,7809],{"class":659,"line":913},[657,7791,4820],{"class":2838},[657,7793,4792],{"class":796},[657,7795,7796],{"class":2875},"\"div.view.current\"",[657,7798,175],{"class":796},[657,7800,4889],{"class":2838},[657,7802,5143],{"class":796},[657,7804,7761],{"class":2838},[657,7806,4792],{"class":796},[657,7808,7766],{"class":2875},[657,7810,5206],{"class":796},[657,7812,7813],{"class":659,"line":919},[657,7814,7815],{"class":796},"});\n",[19,7817,7818,7819,7822,7823,7826],{},"With custom events like ",[460,7820,7821],{},"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 ",[460,7824,7825],{},"#new"," view, you could simply\nwrite:",[649,7828,7830],{"className":4776,"code":7829,"language":4778,"meta":110,"style":110},"$(\"#new\").bind(\"becameActive\", function (event) {\n $(event.target).doSomething();\n});\n",[460,7831,7832,7861,7873],{"__ignoreMap":110},[657,7833,7834,7836,7838,7840,7842,7845,7847,7849,7851,7853,7856,7859],{"class":659,"line":660},[657,7835,7622],{"class":2838},[657,7837,4792],{"class":796},[657,7839,6662],{"class":2875},[657,7841,175],{"class":796},[657,7843,7844],{"class":2838},"bind",[657,7846,4792],{"class":796},[657,7848,7766],{"class":2875},[657,7850,4799],{"class":796},[657,7852,4786],{"class":4785},[657,7854,7855],{"class":796}," (",[657,7857,7858],{"class":4795},"event",[657,7860,4815],{"class":796},[657,7862,7863,7865,7868,7871],{"class":659,"line":111},[657,7864,4820],{"class":2838},[657,7866,7867],{"class":796},"(event.target).",[657,7869,7870],{"class":2838},"doSomething",[657,7872,5125],{"class":796},[657,7874,7875],{"class":659,"line":671},[657,7876,7815],{"class":796},[19,7878,7879],{},"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:",[649,7881,7883],{"className":4776,"code":7882,"language":4778,"meta":110,"style":110},"// 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",[460,7884,7885,7890,7895,7929,7947,7957,7965,7972,7979,7983,7987,7992,8008,8034,8039,8043],{"__ignoreMap":110},[657,7886,7887],{"class":659,"line":660},[657,7888,7889],{"class":5105},"// open a database, providing it's name, version,\n",[657,7891,7892],{"class":659,"line":111},[657,7893,7894],{"class":5105},"// maximum size and display name\n",[657,7896,7897,7899,7902,7904,7907,7909,7912,7914,7917,7919,7922,7924,7927],{"class":659,"line":671},[657,7898,5111],{"class":4785},[657,7900,7901],{"class":796}," database ",[657,7903,2872],{"class":4785},[657,7905,7906],{"class":2838}," openDatabase",[657,7908,4792],{"class":796},[657,7910,7911],{"class":2875},"\"Example\"",[657,7913,4799],{"class":796},[657,7915,7916],{"class":2875},"\"1.0\"",[657,7918,4799],{"class":796},[657,7920,7921],{"class":4865},"1048576",[657,7923,4799],{"class":796},[657,7925,7926],{"class":2875},"\"Example Database\"",[657,7928,5206],{"class":796},[657,7930,7931,7934,7937,7939,7941,7943,7945],{"class":659,"line":677},[657,7932,7933],{"class":796},"database.",[657,7935,7936],{"class":2838},"transaction",[657,7938,4792],{"class":796},[657,7940,4786],{"class":4785},[657,7942,7855],{"class":796},[657,7944,7936],{"class":4795},[657,7946,4815],{"class":796},[657,7948,7949,7952,7955],{"class":659,"line":683},[657,7950,7951],{"class":796}," transaction.",[657,7953,7954],{"class":2838},"executeSQL",[657,7956,4837],{"class":796},[657,7958,7959,7962],{"class":659,"line":732},[657,7960,7961],{"class":2875}," \"CREATE TABLE IF NOT EXISTS data\"",[657,7963,7964],{"class":4785}," +\n",[657,7966,7967,7970],{"class":659,"line":738},[657,7968,7969],{"class":2875}," \"(id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,\"",[657,7971,7964],{"class":4785},[657,7973,7974,7977],{"class":659,"line":744},[657,7975,7976],{"class":2875}," \"... more declarations ...);\"",[657,7978,4845],{"class":796},[657,7980,7981],{"class":659,"line":750},[657,7982,4873],{"class":796},[657,7984,7985],{"class":659,"line":756},[657,7986,7815],{"class":796},[657,7988,7989],{"class":659,"line":907},[657,7990,7991],{"class":5105},"// other statements are issued the same way\n",[657,7993,7994,7996,7998,8000,8002,8004,8006],{"class":659,"line":913},[657,7995,7933],{"class":796},[657,7997,7936],{"class":2838},[657,7999,4792],{"class":796},[657,8001,4786],{"class":4785},[657,8003,7855],{"class":796},[657,8005,7936],{"class":4795},[657,8007,4815],{"class":796},[657,8009,8010,8012,8014,8016,8019,8021,8023,8025,8027,8029,8032],{"class":659,"line":919},[657,8011,7951],{"class":796},[657,8013,7954],{"class":2838},[657,8015,4792],{"class":796},[657,8017,8018],{"class":2875},"\"SELECT * FROM data;\"",[657,8020,4799],{"class":796},[657,8022,4786],{"class":4785},[657,8024,7855],{"class":796},[657,8026,7936],{"class":4795},[657,8028,4799],{"class":796},[657,8030,8031],{"class":4795},"result",[657,8033,4815],{"class":796},[657,8035,8036],{"class":659,"line":1006},[657,8037,8038],{"class":5105}," // do something with 'result'\n",[657,8040,8041],{"class":659,"line":1088},[657,8042,7773],{"class":796},[657,8044,8045],{"class":659,"line":1094},[657,8046,7815],{"class":796},[23,8048,8050],{"id":8049},"putting-it-all-together","Putting it all together",[19,8052,8053,8054,592,8057],{},"A question remains: ",[693,8055,8056],{},"when this is the basic skeleton of an application, how do i put this on an actual\ndevice?",[33,8058],{"alt":110,"src":8059},"https://media.synyx.de/uploads//2010/08/jquery-html-css-example-e1281692547570.png",[19,8061,8062],{},"Example in Simulator",[19,8064,8065,8066,8069],{},"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 ",[152,8067,6085],{"href":4669,"rel":8068},[156],". 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.",[23,8071,8073],{"id":8072},"whats-next","What’s next?",[19,8075,8076],{},"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:",[85,8078,8079,8082,8085,8088],{},[88,8080,8081],{},"What problems might arise with JavaScript as a development language? Is there enough tool support for ease of\ndevelopment?",[88,8083,8084],{},"What do i have to program from scratch and which frameworks are available, that erase my need of boilerplate code?",[88,8086,8087],{},"What about the performance, for example when sophisticated animations are desired?",[88,8089,8090],{},"Is my application really cross-platform, when using these technologies, and does it behave exactly the same on every\ndevice?",[19,8092,8093],{},"Stay tuned for more.",[1341,8095,8096],{},"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":110,"searchDepth":111,"depth":111,"links":8098},[8099,8100,8101,8102],{"id":6571,"depth":111,"text":6572},{"id":6578,"depth":111,"text":6579},{"id":8049,"depth":111,"text":8050},{"id":8072,"depth":111,"text":8073},[120,483],"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":6555,"description":8110},"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",[133,6304,789,5047,4778],"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":8116,"title":8117,"author":8118,"body":8119,"category":8130,"date":8131,"description":110,"extension":124,"link":8132,"meta":8133,"navigation":127,"path":8134,"seo":8135,"slug":8123,"stem":8136,"tags":8137,"teaser":8138,"__hash__":8139},"blog/blog/human-maier-we-are-in-beta.md","Human Maier! We are in Beta!",[5023],{"type":12,"value":8120,"toc":8128},[8121,8124],[15,8122,8117],{"id":8123},"human-maier-we-are-in-beta",[19,8125,8126],{},[33,8127],{"alt":5033,"src":5871},{"title":110,"searchDepth":111,"depth":111,"links":8129},[],[120,263],"2010-07-30T14:30:44","https://synyx.de/blog/human-maier-we-are-in-beta/",{},"/blog/human-maier-we-are-in-beta",{"title":8117,"description":110},"blog/human-maier-we-are-in-beta",[133,5046,5047],"Our little pet project “I think I spider” is almost ready to be thrown out into the wild! That means we are running a closed beta for iOS devices for…","Tv3D01DCynO0dlp6K4oLCcEF6qiHcJkQCehI25ZSfc0",{"id":8141,"title":8142,"author":8143,"body":8144,"category":8454,"date":8455,"description":8456,"extension":124,"link":8457,"meta":8458,"navigation":127,"path":8459,"seo":8460,"slug":8148,"stem":8462,"tags":8463,"teaser":8464,"__hash__":8465},"blog/blog/sending-apple-push-notifications-with-notnoops-java-apns-library.md","Sending Apple Push Notifications with notnoop's java-apns library",[617],{"type":12,"value":8145,"toc":8452},[8146,8149,8164,8172,8175,8182,8185,8447,8450],[15,8147,8142],{"id":8148},"sending-apple-push-notifications-with-notnoops-java-apns-library",[19,8150,8151,8152,8157,8158,8163],{},"If you need to send apple push notifications to your users, like we do in\na ",[152,8153,8156],{"href":8154,"rel":8155},"http://mobile.synyx.de/2010/07/i-think-i-spider/",[156],"secret project"," mentioned earlier this\nweek, ",[152,8159,8162],{"href":8160,"rel":8161},"http://github.com/notnoop/java-apns",[156],"notnoop’s java-apns library"," is a good choice, because its really simple to\nuse and saves you a lot of work.",[19,8165,8166,8167,700],{},"(I presume that you already know how to get the Tokens of your users and already have a certificate for the push\nnotifications, I only show you how easy the server part can be with this library. If you don’t know, read this\nfirst: ",[152,8168,8171],{"href":8169,"rel":8170},"http://developer.apple.com/iphone/library/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/ApplePushService/ApplePushService.html",[156],"Apple Push Service",[19,8173,8174],{},"First you have to download the library (or add it in your maven dependencies):",[19,8176,8177],{},[152,8178,8181],{"href":8179,"rel":8180},"http://github.com/notnoop/java-apns/tree/",[156],"java-apns",[19,8183,8184],{},"The programming part isn’t that much so here’s how you do it:",[649,8186,8188],{"className":2130,"code":8187,"language":2132,"meta":110,"style":110},"\npublic void pushMessage() {\n ApnsService service = null;\n try {\n // get the certificate\n InputStream certStream = this.getClass().getClassLoader().getResourceAsStream(\"your_certificate.p12\");\n service = APNS.newService().withCert(certStream, \"your_cert_password\").withSandboxDestination().build();\n // or\n // service = APNS.newService().withCert(certStream,\n // \"your_cert_password\").withProductionDestination().build();\n service.start();\n // You have to delete the devices from you list that no longer\n //have the app installed, see method below\n deleteInactiveDevices(service);\n // read your user list\n List\u003CUser> userList = userDao.readUsers();\n for (User user : userList) {\n try {\n // we had a daily update here, so we need to know how many\n //days the user hasn't started the app\n // so that we get the number of updates to display it as the badge.\n int days = (int) ((System.currentTimeMillis() - user.getLastUpdate()) / 1000 / 60 / 60 / 24);\n PayloadBuilder payloadBuilder = APNS.newPayload();\n payloadBuilder = payloadBuilder.badge(days).alertBody(\"some message you want to send here\");\n // check if the message is too long (it won't be sent if it is)\n //and trim it if it is.\n if (payloadBuilder.isTooLong()) {\n payloadBuilder = payloadBuilder.shrinkBody();\n }\n String payload = payloadBuilder.build();\n String token = user.getToken();\n service.push(token, payload);\n } catch (Exception ex) {\n // some logging stuff\n }\n }\n } catch (Exception ex) {\n // more logging\n } finally {\n // check if the service was successfull initialized and stop it here, if it was\n if (service != null) {\n service.stop();\n }\n }\n }\n private void deleteInactiveDevices(ApnsService service) {\n // get the list of the devices that no longer have your app installed from apple\n //ignore the =\"\" after Date here, it's a bug...\n Map\u003CString, Date> inactiveDevices = service.getInactiveDevices();\n for (String deviceToken : inactiveDevices.keySet()) {\n userDao.deleteByDeviceId(deviceToken);\n }\n }\n\n",[460,8189,8190,8194,8199,8204,8208,8213,8218,8223,8228,8233,8238,8243,8248,8253,8258,8263,8268,8273,8278,8283,8288,8293,8298,8303,8308,8313,8318,8323,8328,8333,8338,8343,8348,8353,8358,8363,8367,8372,8377,8382,8387,8392,8397,8401,8405,8409,8414,8419,8424,8429,8434,8439,8443],{"__ignoreMap":110},[657,8191,8192],{"class":659,"line":660},[657,8193,663],{"emptyLinePlaceholder":127},[657,8195,8196],{"class":659,"line":111},[657,8197,8198],{},"public void pushMessage() {\n",[657,8200,8201],{"class":659,"line":671},[657,8202,8203],{}," ApnsService service = null;\n",[657,8205,8206],{"class":659,"line":677},[657,8207,3298],{},[657,8209,8210],{"class":659,"line":683},[657,8211,8212],{}," // get the certificate\n",[657,8214,8215],{"class":659,"line":732},[657,8216,8217],{}," InputStream certStream = this.getClass().getClassLoader().getResourceAsStream(\"your_certificate.p12\");\n",[657,8219,8220],{"class":659,"line":738},[657,8221,8222],{}," service = APNS.newService().withCert(certStream, \"your_cert_password\").withSandboxDestination().build();\n",[657,8224,8225],{"class":659,"line":744},[657,8226,8227],{}," // or\n",[657,8229,8230],{"class":659,"line":750},[657,8231,8232],{}," // service = APNS.newService().withCert(certStream,\n",[657,8234,8235],{"class":659,"line":756},[657,8236,8237],{}," // \"your_cert_password\").withProductionDestination().build();\n",[657,8239,8240],{"class":659,"line":907},[657,8241,8242],{}," service.start();\n",[657,8244,8245],{"class":659,"line":913},[657,8246,8247],{}," // You have to delete the devices from you list that no longer\n",[657,8249,8250],{"class":659,"line":919},[657,8251,8252],{}," //have the app installed, see method below\n",[657,8254,8255],{"class":659,"line":1006},[657,8256,8257],{}," deleteInactiveDevices(service);\n",[657,8259,8260],{"class":659,"line":1088},[657,8261,8262],{}," // read your user list\n",[657,8264,8265],{"class":659,"line":1094},[657,8266,8267],{}," List\u003CUser> userList = userDao.readUsers();\n",[657,8269,8270],{"class":659,"line":1100},[657,8271,8272],{}," for (User user : userList) {\n",[657,8274,8275],{"class":659,"line":1106},[657,8276,8277],{}," try {\n",[657,8279,8280],{"class":659,"line":1112},[657,8281,8282],{}," // we had a daily update here, so we need to know how many\n",[657,8284,8285],{"class":659,"line":1118},[657,8286,8287],{}," //days the user hasn't started the app\n",[657,8289,8290],{"class":659,"line":1124},[657,8291,8292],{}," // so that we get the number of updates to display it as the badge.\n",[657,8294,8295],{"class":659,"line":1130},[657,8296,8297],{}," int days = (int) ((System.currentTimeMillis() - user.getLastUpdate()) / 1000 / 60 / 60 / 24);\n",[657,8299,8300],{"class":659,"line":1136},[657,8301,8302],{}," PayloadBuilder payloadBuilder = APNS.newPayload();\n",[657,8304,8305],{"class":659,"line":1142},[657,8306,8307],{}," payloadBuilder = payloadBuilder.badge(days).alertBody(\"some message you want to send here\");\n",[657,8309,8310],{"class":659,"line":1148},[657,8311,8312],{}," // check if the message is too long (it won't be sent if it is)\n",[657,8314,8315],{"class":659,"line":1154},[657,8316,8317],{}," //and trim it if it is.\n",[657,8319,8320],{"class":659,"line":1160},[657,8321,8322],{}," if (payloadBuilder.isTooLong()) {\n",[657,8324,8325],{"class":659,"line":1166},[657,8326,8327],{}," payloadBuilder = payloadBuilder.shrinkBody();\n",[657,8329,8330],{"class":659,"line":1172},[657,8331,8332],{}," }\n",[657,8334,8335],{"class":659,"line":1178},[657,8336,8337],{}," String payload = payloadBuilder.build();\n",[657,8339,8340],{"class":659,"line":1183},[657,8341,8342],{}," String token = user.getToken();\n",[657,8344,8345],{"class":659,"line":1188},[657,8346,8347],{}," service.push(token, payload);\n",[657,8349,8350],{"class":659,"line":1194},[657,8351,8352],{}," } catch (Exception ex) {\n",[657,8354,8355],{"class":659,"line":1199},[657,8356,8357],{}," // some logging stuff\n",[657,8359,8360],{"class":659,"line":1205},[657,8361,8362],{}," }\n",[657,8364,8365],{"class":659,"line":1210},[657,8366,4255],{},[657,8368,8369],{"class":659,"line":1215},[657,8370,8371],{}," } catch (Exception ex) {\n",[657,8373,8374],{"class":659,"line":1220},[657,8375,8376],{}," // more logging\n",[657,8378,8379],{"class":659,"line":1225},[657,8380,8381],{}," } finally {\n",[657,8383,8384],{"class":659,"line":1230},[657,8385,8386],{}," // check if the service was successfull initialized and stop it here, if it was\n",[657,8388,8389],{"class":659,"line":1236},[657,8390,8391],{}," if (service != null) {\n",[657,8393,8394],{"class":659,"line":7370},[657,8395,8396],{}," service.stop();\n",[657,8398,8399],{"class":659,"line":7375},[657,8400,4255],{},[657,8402,8403],{"class":659,"line":7390},[657,8404,2178],{},[657,8406,8407],{"class":659,"line":7406},[657,8408,2188],{},[657,8410,8411],{"class":659,"line":7420},[657,8412,8413],{}," private void deleteInactiveDevices(ApnsService service) {\n",[657,8415,8416],{"class":659,"line":7425},[657,8417,8418],{}," // get the list of the devices that no longer have your app installed from apple\n",[657,8420,8421],{"class":659,"line":7440},[657,8422,8423],{}," //ignore the =\"\" after Date here, it's a bug...\n",[657,8425,8426],{"class":659,"line":7456},[657,8427,8428],{}," Map\u003CString, Date> inactiveDevices = service.getInactiveDevices();\n",[657,8430,8431],{"class":659,"line":7467},[657,8432,8433],{}," for (String deviceToken : inactiveDevices.keySet()) {\n",[657,8435,8436],{"class":659,"line":7480},[657,8437,8438],{}," userDao.deleteByDeviceId(deviceToken);\n",[657,8440,8441],{"class":659,"line":7493},[657,8442,2178],{},[657,8444,8445],{"class":659,"line":7508},[657,8446,2188],{},[19,8448,8449],{},"Now wasn’t that an easy one this time?",[1341,8451,2074],{},{"title":110,"searchDepth":111,"depth":111,"links":8453},[],[120],"2010-07-27T07:00:38","If you need to send apple push notifications to your users, like we do in\\na secret project mentioned earlier this\\nweek, notnoop’s java-apns library is a good choice, because its really simple to\\nuse and saves you a lot of work.","https://synyx.de/blog/sending-apple-push-notifications-with-notnoops-java-apns-library/",{},"/blog/sending-apple-push-notifications-with-notnoops-java-apns-library",{"title":8142,"description":8461},"If you need to send apple push notifications to your users, like we do in\na secret project mentioned earlier this\nweek, notnoop’s java-apns library is a good choice, because its really simple to\nuse and saves you a lot of work.","blog/sending-apple-push-notifications-with-notnoops-java-apns-library",[5882,5046,5047,3406],"If you need to send apple push notifications to your users, like we do in a secret project mentioned earlier this week, notnoop’s java-apns library is a good choice, because…","mqOSmpufU8Sqpzy9fxCtmH5D6V8CLhPDD5kkCRFFWO8",{"id":8467,"title":8468,"author":8469,"body":8470,"category":8480,"date":8481,"description":110,"extension":124,"link":8482,"meta":8483,"navigation":127,"path":8484,"seo":8485,"slug":6022,"stem":8486,"tags":8487,"teaser":8488,"__hash__":8489},"blog/blog/i-think-i-spider.md","I think I spider",[5023],{"type":12,"value":8471,"toc":8478},[8472,8474],[15,8473,8468],{"id":6022},[19,8475,8476],{},[33,8477],{"alt":5033,"src":5871},{"title":110,"searchDepth":111,"depth":111,"links":8479},[],[120,263],"2010-07-26T06:56:04","https://synyx.de/blog/i-think-i-spider/",{},"/blog/i-think-i-spider",{"title":8468,"description":110},"blog/i-think-i-spider",[133,5047],"We are working on a fair number of Apps here at our mobile team and today we are proud to announce one of them: I think I spider! The “I…","Q4uy3XPVMpWwUGobhts0mEDqawiYKLMPBPP9qYTF_us",{"id":8491,"title":8492,"author":8493,"body":8495,"category":8600,"date":8601,"description":8602,"extension":124,"link":8603,"meta":8604,"navigation":127,"path":8605,"seo":8606,"slug":8499,"stem":8607,"tags":8608,"teaser":8610,"__hash__":8611},"blog/blog/debugging-on-your-n900.md","Debugging on your N900",[8494],"gast",{"type":12,"value":8496,"toc":8598},[8497,8500,8503,8506,8509,8512,8519,8522,8525,8528,8531,8534,8537,8542,8545,8548,8551,8554,8557,8560,8563,8566,8569,8572,8575,8578,8581,8587,8590],[15,8498,8492],{"id":8499},"debugging-on-your-n900",[19,8501,8502],{},"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.",[19,8504,8505],{},"I had to install the „maemo pc connectivity“ Package on my N900 and on my host pc.",[19,8507,8508],{},"First I enabled the developer repository on my N900:",[19,8510,8511],{},"`catalog name: extras-devel",[19,8513,8514,8515],{},"web address: ",[152,8516,8517],{"href":8517,"rel":8518},"http://repository.maemo.org/extras-devel",[156],[19,8520,8521],{},"distribution: fremantle",[19,8523,8524],{},"components: free non-free`",[19,8526,8527],{},"in a shell on the N900:",[19,8529,8530],{},"`sudo gainroot",[19,8532,8533],{},"apt-get install maemo-pc-connectivity gdb`",[19,8535,8536],{},"On my Host System I added the repository:",[19,8538,8539],{},[460,8540,8541],{},"deb http://pc-connectivity.garage.maemo.org/repository intrepid main",[19,8543,8544],{},"and installed host-pc-connectivity by typing:",[19,8546,8547],{},"`sudo apt-get update",[19,8549,8550],{},"sudo apt-get instsall host-pc-connectivity`",[19,8552,8553],{},"I had a new usb device with an IP address 192.168.2.14.",[19,8555,8556],{},"On my N900 I configured in the system-settings->pc connectivity manager the “default” environment:",[19,8558,8559],{},"`connection type: usb",[19,8561,8562],{},"ipadress: 192.168.2.15",[19,8564,8565],{},"gateway: 192.168.2.14`",[19,8567,8568],{},"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.",[19,8570,8571],{},"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",[19,8573,8574],{},"`Download Method: ssh",[19,8576,8577],{},"RemoteConnection: 192.168.2.15`",[19,8579,8580],{},"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.",[19,8582,8583],{},[33,8584],{"alt":8585,"src":8586},"\"Debug Configuration - Download Method\"","https://media.synyx.de/uploads//2010/07/maemo.jpeg",[19,8588,8589],{},"Debug Configuration - Download Method",[19,8591,8592,8593,8597],{},"That was all. The complete documentation of “pc connectivity” can be found here ",[152,8594,8595],{"href":8595,"rel":8596},"http://pc",[156],"\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":110,"searchDepth":111,"depth":111,"links":8599},[],[120,483],"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":8492,"description":8502},"blog/debugging-on-your-n900",[8609],"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":8613,"title":8614,"author":8615,"body":8616,"category":8698,"date":8699,"description":8700,"extension":124,"link":8701,"meta":8702,"navigation":127,"path":8703,"seo":8704,"slug":8620,"stem":8706,"tags":8707,"teaser":8708,"__hash__":8709},"blog/blog/split-nsstring-by-characters.md","Split NSString by characters",[5023],{"type":12,"value":8617,"toc":8696},[8618,8621,8643,8652,8691,8694],[15,8619,8614],{"id":8620},"split-nsstring-by-characters",[19,8622,8623,8624,8631,8632,8634,8635,8642],{},"Have you ever pondered on the ",[152,8625,8628],{"href":8626,"rel":8627},"http://developer.apple.com/iphone/library/documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/Reference/NSString.html",[156],[693,8629,8630],{},"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 ",[693,8633,8630],{}," by characters and create a ",[152,8636,8639],{"href":8637,"rel":8638},"http://developer.apple.com/iphone/library/documentation/Cocoa/Reference/Foundation/Classes/NSArray_Class/NSArray.html",[156],[693,8640,8641],{},"NSArray","\nof those characters.",[19,8644,8645,8646,8651],{},"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 ",[152,8647,8650],{"href":8648,"rel":8649},"http://www.idev101.com/code/Objective-C/Strings/split.html",[156],"this site",", which shows you how to do it:",[649,8653,8655],{"className":6348,"code":8654,"language":6350,"meta":110,"style":110},"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",[460,8656,8657,8662,8667,8672,8677,8682,8687],{"__ignoreMap":110},[657,8658,8659],{"class":659,"line":660},[657,8660,8661],{},"NSMutableArray *characters =\n",[657,8663,8664],{"class":659,"line":111},[657,8665,8666],{},"[[NSMutableArray alloc] initWithCapacity:[myString length]];\n",[657,8668,8669],{"class":659,"line":671},[657,8670,8671],{},"for (int i=0; i \u003C [myString length]; i++) {\n",[657,8673,8674],{"class":659,"line":677},[657,8675,8676],{}," NSString *ichar =\n",[657,8678,8679],{"class":659,"line":683},[657,8680,8681],{},"[NSString stringWithFormat:@\"%c\", [myString characterAtIndex:i]];\n",[657,8683,8684],{"class":659,"line":732},[657,8685,8686],{}," [characters addObject:ichar];\n",[657,8688,8689],{"class":659,"line":738},[657,8690,2438],{},[19,8692,8693],{},"Although Apple’s documentation is extensive, you sometimes need a little hint or guidance to achieve your goal.",[1341,8695,2074],{},{"title":110,"searchDepth":111,"depth":111,"links":8697},[],[120,483],"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":8614,"description":8705},"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",[5370],"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":8711,"title":8712,"author":8713,"body":8714,"category":8954,"date":8955,"description":8956,"extension":124,"link":8957,"meta":8958,"navigation":127,"path":8959,"seo":8960,"slug":8718,"stem":8962,"tags":8963,"teaser":8966,"__hash__":8967},"blog/blog/ui-prototyping-iphone-apps.md","UI Prototyping iPhone Apps",[5023],{"type":12,"value":8715,"toc":8952},[8716,8719,8727,8730,8739,8750,8803,8813,8936,8939,8942,8950],[15,8717,8712],{"id":8718},"ui-prototyping-iphone-apps",[19,8720,8721,8722,8726],{},"Before ",[152,8723,8725],{"href":5065,"rel":8724},[156],"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!",[19,8728,8729],{},"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.",[19,8731,8732,8733,8738],{},"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 ",[152,8734,8737],{"href":8735,"rel":8736},"https://web.archive.org/web/20130723100234/http://www.fruitstandsoftware.com:80/blog/2009/07/uiview-manipulation-made-easier-with-a-category/",[156],"Michael Fey’s blog",",\nwho was able to successfully reverse engineer the missing parts, which were not shown in the presentation.",[19,8740,8741,8742,8745,8746,8749],{},"Michael’s ",[693,8743,8744],{},"UIViewAdditions"," basically allow easy access to frame properties and give you a neat init method, which adds\nthe passed ",[693,8747,8748],{},"UIView"," as a parent:",[649,8751,8753],{"className":6348,"code":8752,"language":6350,"meta":110,"style":110},"- (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",[460,8754,8755,8760,8765,8770,8775,8780,8785,8789,8794,8799],{"__ignoreMap":110},[657,8756,8757],{"class":659,"line":660},[657,8758,8759],{},"- (id)initWithParent:(UIView *)parent {\n",[657,8761,8762],{"class":659,"line":111},[657,8763,8764],{}," self = [self initWithFrame:CGRectZero];\n",[657,8766,8767],{"class":659,"line":671},[657,8768,8769],{}," if (!self)\n",[657,8771,8772],{"class":659,"line":677},[657,8773,8774],{}," return nil;\n",[657,8776,8777],{"class":659,"line":683},[657,8778,8779],{}," [parent addSubview:self];\n",[657,8781,8782],{"class":659,"line":732},[657,8783,8784],{}," return self;\n",[657,8786,8787],{"class":659,"line":738},[657,8788,2438],{},[657,8790,8791],{"class":659,"line":744},[657,8792,8793],{},"+ (id) viewWithParent:(UIView *)parent {\n",[657,8795,8796],{"class":659,"line":750},[657,8797,8798],{}," return [[[self alloc] initWithParent:parent] autorelease];\n",[657,8800,8801],{"class":659,"line":756},[657,8802,2438],{},[19,8804,8805,8806,8809,8810,8812],{},"There wasn’t much left to do for me. I only coded the class ",[693,8807,8808],{},"Root",", which is the parent of all ",[693,8811,5295],{}," instances\nused in the prototype. It provides a couple of methods to slide images back and forth:",[649,8814,8816],{"className":6348,"code":8815,"language":6350,"meta":110,"style":110},"@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",[460,8817,8818,8823,8828,8833,8838,8842,8847,8852,8857,8862,8867,8871,8875,8880,8885,8890,8894,8899,8904,8909,8914,8918,8922,8927,8932],{"__ignoreMap":110},[657,8819,8820],{"class":659,"line":660},[657,8821,8822],{},"@synthesize pageIndex = _pageIndex;\n",[657,8824,8825],{"class":659,"line":111},[657,8826,8827],{},"- (id) initWithParent:(UIView *)parent {\n",[657,8829,8830],{"class":659,"line":671},[657,8831,8832],{}," self = [super initWithParent:parent];\n",[657,8834,8835],{"class":659,"line":677},[657,8836,8837],{}," if (self == nil) {\n",[657,8839,8840],{"class":659,"line":683},[657,8841,8774],{},[657,8843,8844],{"class":659,"line":732},[657,8845,8846],{}," }\n",[657,8848,8849],{"class":659,"line":738},[657,8850,8851],{}," self.userInteractionEnabled = YES;\n",[657,8853,8854],{"class":659,"line":744},[657,8855,8856],{}," self.size = self.window.size;\n",[657,8858,8859],{"class":659,"line":750},[657,8860,8861],{}," [[UIImageView viewWithParent:self] setImageWithName:@\"dailies\"];\n",[657,8863,8864],{"class":659,"line":756},[657,8865,8866],{}," self.pageIndex = 0;\n",[657,8868,8869],{"class":659,"line":907},[657,8870,8784],{},[657,8872,8873],{"class":659,"line":913},[657,8874,2438],{},[657,8876,8877],{"class":659,"line":919},[657,8878,8879],{},"- (void)setPageIndex:(int)index {\n",[657,8881,8882],{"class":659,"line":1006},[657,8883,8884],{}," if (index \u003C 0 || index >= [self.subviews count]) {\n",[657,8886,8887],{"class":659,"line":1088},[657,8888,8889],{}," return;\n",[657,8891,8892],{"class":659,"line":1094},[657,8893,8846],{},[657,8895,8896],{"class":659,"line":1100},[657,8897,8898],{}," _pageIndex = index;\n",[657,8900,8901],{"class":659,"line":1106},[657,8902,8903],{}," for (int i = 0; i \u003C [self.subviews count]; i++) {\n",[657,8905,8906],{"class":659,"line":1112},[657,8907,8908],{}," UIImageView *page = [self.subviews objectAtIndex:i];\n",[657,8910,8911],{"class":659,"line":1118},[657,8912,8913],{}," page.x = (i \u003C _pageIndex) ? -self.width : (i > _pageIndex) ? self.width : 0;\n",[657,8915,8916],{"class":659,"line":1124},[657,8917,8846],{},[657,8919,8920],{"class":659,"line":1130},[657,8921,2438],{},[657,8923,8924],{"class":659,"line":1136},[657,8925,8926],{},"- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {\n",[657,8928,8929],{"class":659,"line":1142},[657,8930,8931],{}," self.pageIndex++;\n",[657,8933,8934],{"class":659,"line":1148},[657,8935,2438],{},[19,8937,8938],{},"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:",[19,8940,8941],{},"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.",[19,8943,8944,8945,5074],{},"You can download the source code for the two classes, along with a sample project\nfrom ",[152,8946,8949],{"href":8947,"rel":8948},"http://github.com/dlinsin/district9/tree/master/UIPrototyping/",[156],"github",[1341,8951,2074],{},{"title":110,"searchDepth":111,"depth":111,"links":8953},[],[120,483],"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":8712,"description":8961},"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",[5046,8964,5047,5370,8965],"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":8969,"title":8970,"author":8971,"body":8972,"category":10115,"date":10116,"description":10117,"extension":124,"link":10118,"meta":10119,"navigation":127,"path":10120,"seo":10121,"slug":8976,"stem":10122,"tags":10123,"teaser":10129,"__hash__":10130},"blog/blog/android-and-self-signed-ssl-certificates.md","Android and self-signed ssl certificates",[617],{"type":12,"value":8973,"toc":10113},[8974,8977,8980,8983,8986,9564,9567,9882,9891,9894,9923,10065,10108,10111],[15,8975,8970],{"id":8976},"android-and-self-signed-ssl-certificates",[19,8978,8979],{},"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.",[19,8981,8982],{},"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.",[19,8984,8985],{},"First you have to create the SSLFactory:",[649,8987,8989],{"className":2130,"code":8988,"language":2132,"meta":110,"style":110},"\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",[460,8990,8991,8995,9000,9005,9010,9015,9020,9025,9030,9035,9040,9045,9049,9054,9059,9064,9069,9074,9079,9084,9089,9094,9099,9104,9109,9114,9119,9124,9129,9134,9139,9144,9149,9154,9159,9163,9168,9173,9178,9182,9187,9192,9197,9201,9206,9211,9216,9221,9226,9230,9234,9239,9244,9249,9253,9258,9262,9267,9272,9277,9282,9287,9292,9298,9304,9310,9316,9322,9328,9334,9340,9345,9351,9357,9362,9368,9374,9380,9385,9390,9396,9401,9407,9413,9418,9423,9429,9434,9440,9446,9451,9456,9462,9468,9473,9479,9485,9491,9496,9502,9508,9514,9520,9525,9531,9537,9542,9548,9554,9559],{"__ignoreMap":110},[657,8992,8993],{"class":659,"line":660},[657,8994,663],{"emptyLinePlaceholder":127},[657,8996,8997],{"class":659,"line":111},[657,8998,8999],{},"/*\n",[657,9001,9002],{"class":659,"line":671},[657,9003,9004],{}," * Licensed to the Apache Software Foundation (ASF) under one\n",[657,9006,9007],{"class":659,"line":677},[657,9008,9009],{}," * or more contributor license agreements. See the NOTICE file\n",[657,9011,9012],{"class":659,"line":683},[657,9013,9014],{}," * distributed with this work for additional information\n",[657,9016,9017],{"class":659,"line":732},[657,9018,9019],{}," * regarding copyright ownership. The ASF licenses this file\n",[657,9021,9022],{"class":659,"line":738},[657,9023,9024],{}," * to you under the Apache License, Version 2.0 (the\n",[657,9026,9027],{"class":659,"line":744},[657,9028,9029],{}," * \"License\"); you may not use this file except in compliance\n",[657,9031,9032],{"class":659,"line":750},[657,9033,9034],{}," * with the License. You may obtain a copy of the License at\n",[657,9036,9037],{"class":659,"line":756},[657,9038,9039],{}," *\n",[657,9041,9042],{"class":659,"line":907},[657,9043,9044],{}," * http://www.apache.org/licenses/LICENSE-2.0\n",[657,9046,9047],{"class":659,"line":913},[657,9048,9039],{},[657,9050,9051],{"class":659,"line":919},[657,9052,9053],{}," * Unless required by applicable law or agreed to in writing,\n",[657,9055,9056],{"class":659,"line":1006},[657,9057,9058],{}," * software distributed under the License is distributed on an\n",[657,9060,9061],{"class":659,"line":1088},[657,9062,9063],{}," * \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n",[657,9065,9066],{"class":659,"line":1094},[657,9067,9068],{}," * KIND, either express or implied. See the License for the\n",[657,9070,9071],{"class":659,"line":1100},[657,9072,9073],{}," * specific language governing permissions and limitations\n",[657,9075,9076],{"class":659,"line":1106},[657,9077,9078],{}," * under the License.\n",[657,9080,9081],{"class":659,"line":1112},[657,9082,9083],{}," */\n",[657,9085,9086],{"class":659,"line":1118},[657,9087,9088],{},"import java.io.IOException;\n",[657,9090,9091],{"class":659,"line":1124},[657,9092,9093],{},"import java.net.InetAddress;\n",[657,9095,9096],{"class":659,"line":1130},[657,9097,9098],{},"import java.net.InetSocketAddress;\n",[657,9100,9101],{"class":659,"line":1136},[657,9102,9103],{},"import java.net.Socket;\n",[657,9105,9106],{"class":659,"line":1142},[657,9107,9108],{},"import java.net.UnknownHostException;\n",[657,9110,9111],{"class":659,"line":1148},[657,9112,9113],{},"import javax.net.ssl.SSLContext;\n",[657,9115,9116],{"class":659,"line":1154},[657,9117,9118],{},"import javax.net.ssl.SSLSocket;\n",[657,9120,9121],{"class":659,"line":1160},[657,9122,9123],{},"import javax.net.ssl.TrustManager;\n",[657,9125,9126],{"class":659,"line":1166},[657,9127,9128],{},"import org.apache.http.conn.ConnectTimeoutException;\n",[657,9130,9131],{"class":659,"line":1172},[657,9132,9133],{},"import org.apache.http.conn.scheme.LayeredSocketFactory;\n",[657,9135,9136],{"class":659,"line":1178},[657,9137,9138],{},"import org.apache.http.conn.scheme.SocketFactory;\n",[657,9140,9141],{"class":659,"line":1183},[657,9142,9143],{},"import org.apache.http.params.HttpConnectionParams;\n",[657,9145,9146],{"class":659,"line":1188},[657,9147,9148],{},"import org.apache.http.params.HttpParams;\n",[657,9150,9151],{"class":659,"line":1194},[657,9152,9153],{},"/**\n",[657,9155,9156],{"class":659,"line":1199},[657,9157,9158],{}," * This socket factory will create ssl socket that accepts self signed certificate\n",[657,9160,9161],{"class":659,"line":1205},[657,9162,9039],{},[657,9164,9165],{"class":659,"line":1210},[657,9166,9167],{}," * @author olamy\n",[657,9169,9170],{"class":659,"line":1215},[657,9171,9172],{}," * @version $Id: EasySSLSocketFactory.java 765355 2009-04-15 20:59:07Z evenisse $\n",[657,9174,9175],{"class":659,"line":1220},[657,9176,9177],{}," * @since 1.2.3\n",[657,9179,9180],{"class":659,"line":1225},[657,9181,9083],{},[657,9183,9184],{"class":659,"line":1230},[657,9185,9186],{},"public class EasySSLSocketFactory implements SocketFactory, LayeredSocketFactory {\n",[657,9188,9189],{"class":659,"line":1236},[657,9190,9191],{}," private SSLContext sslcontext = null;\n",[657,9193,9194],{"class":659,"line":7370},[657,9195,9196],{}," private static SSLContext createEasySSLContext() throws IOException {\n",[657,9198,9199],{"class":659,"line":7375},[657,9200,3298],{},[657,9202,9203],{"class":659,"line":7390},[657,9204,9205],{}," SSLContext context = SSLContext.getInstance(\"TLS\");\n",[657,9207,9208],{"class":659,"line":7406},[657,9209,9210],{}," context.init(null, new TrustManager[] { new EasyX509TrustManager(null) }, null);\n",[657,9212,9213],{"class":659,"line":7420},[657,9214,9215],{}," return context;\n",[657,9217,9218],{"class":659,"line":7425},[657,9219,9220],{}," } catch (Exception e) {\n",[657,9222,9223],{"class":659,"line":7440},[657,9224,9225],{}," throw new IOException(e.getMessage());\n",[657,9227,9228],{"class":659,"line":7456},[657,9229,2178],{},[657,9231,9232],{"class":659,"line":7467},[657,9233,2188],{},[657,9235,9236],{"class":659,"line":7480},[657,9237,9238],{}," private SSLContext getSSLContext() throws IOException {\n",[657,9240,9241],{"class":659,"line":7493},[657,9242,9243],{}," if (this.sslcontext == null) {\n",[657,9245,9246],{"class":659,"line":7508},[657,9247,9248],{}," this.sslcontext = createEasySSLContext();\n",[657,9250,9251],{"class":659,"line":7522},[657,9252,2178],{},[657,9254,9255],{"class":659,"line":7527},[657,9256,9257],{}," return this.sslcontext;\n",[657,9259,9260],{"class":659,"line":7533},[657,9261,2188],{},[657,9263,9264],{"class":659,"line":7543},[657,9265,9266],{}," /**\n",[657,9268,9269],{"class":659,"line":7554},[657,9270,9271],{}," * @see org.apache.http.conn.scheme.SocketFactory#connectSocket(java.net.Socket, java.lang.String, int,\n",[657,9273,9274],{"class":659,"line":7567},[657,9275,9276],{}," * java.net.InetAddress, int, org.apache.http.params.HttpParams)\n",[657,9278,9279],{"class":659,"line":7580},[657,9280,9281],{}," */\n",[657,9283,9284],{"class":659,"line":7593},[657,9285,9286],{}," public Socket connectSocket(Socket sock, String host, int port, InetAddress localAddress, int localPort,\n",[657,9288,9289],{"class":659,"line":7607},[657,9290,9291],{}," HttpParams params) throws IOException, UnknownHostException, ConnectTimeoutException {\n",[657,9293,9295],{"class":659,"line":9294},63,[657,9296,9297],{}," int connTimeout = HttpConnectionParams.getConnectionTimeout(params);\n",[657,9299,9301],{"class":659,"line":9300},64,[657,9302,9303],{}," int soTimeout = HttpConnectionParams.getSoTimeout(params);\n",[657,9305,9307],{"class":659,"line":9306},65,[657,9308,9309],{}," InetSocketAddress remoteAddress = new InetSocketAddress(host, port);\n",[657,9311,9313],{"class":659,"line":9312},66,[657,9314,9315],{}," SSLSocket sslsock = (SSLSocket) ((sock != null) ? sock : createSocket());\n",[657,9317,9319],{"class":659,"line":9318},67,[657,9320,9321],{}," if ((localAddress != null) || (localPort > 0)) {\n",[657,9323,9325],{"class":659,"line":9324},68,[657,9326,9327],{}," // we need to bind explicitly\n",[657,9329,9331],{"class":659,"line":9330},69,[657,9332,9333],{}," if (localPort \u003C 0) {\n",[657,9335,9337],{"class":659,"line":9336},70,[657,9338,9339],{}," localPort = 0; // indicates \"any\"\n",[657,9341,9343],{"class":659,"line":9342},71,[657,9344,4255],{},[657,9346,9348],{"class":659,"line":9347},72,[657,9349,9350],{}," InetSocketAddress isa = new InetSocketAddress(localAddress, localPort);\n",[657,9352,9354],{"class":659,"line":9353},73,[657,9355,9356],{}," sslsock.bind(isa);\n",[657,9358,9360],{"class":659,"line":9359},74,[657,9361,2178],{},[657,9363,9365],{"class":659,"line":9364},75,[657,9366,9367],{}," sslsock.connect(remoteAddress, connTimeout);\n",[657,9369,9371],{"class":659,"line":9370},76,[657,9372,9373],{}," sslsock.setSoTimeout(soTimeout);\n",[657,9375,9377],{"class":659,"line":9376},77,[657,9378,9379],{}," return sslsock;\n",[657,9381,9383],{"class":659,"line":9382},78,[657,9384,2188],{},[657,9386,9388],{"class":659,"line":9387},79,[657,9389,9266],{},[657,9391,9393],{"class":659,"line":9392},80,[657,9394,9395],{}," * @see org.apache.http.conn.scheme.SocketFactory#createSocket()\n",[657,9397,9399],{"class":659,"line":9398},81,[657,9400,9281],{},[657,9402,9404],{"class":659,"line":9403},82,[657,9405,9406],{}," public Socket createSocket() throws IOException {\n",[657,9408,9410],{"class":659,"line":9409},83,[657,9411,9412],{}," return getSSLContext().getSocketFactory().createSocket();\n",[657,9414,9416],{"class":659,"line":9415},84,[657,9417,2188],{},[657,9419,9421],{"class":659,"line":9420},85,[657,9422,9266],{},[657,9424,9426],{"class":659,"line":9425},86,[657,9427,9428],{}," * @see org.apache.http.conn.scheme.SocketFactory#isSecure(java.net.Socket)\n",[657,9430,9432],{"class":659,"line":9431},87,[657,9433,9281],{},[657,9435,9437],{"class":659,"line":9436},88,[657,9438,9439],{}," public boolean isSecure(Socket socket) throws IllegalArgumentException {\n",[657,9441,9443],{"class":659,"line":9442},89,[657,9444,9445],{}," return true;\n",[657,9447,9449],{"class":659,"line":9448},90,[657,9450,2188],{},[657,9452,9454],{"class":659,"line":9453},91,[657,9455,9266],{},[657,9457,9459],{"class":659,"line":9458},92,[657,9460,9461],{}," * @see org.apache.http.conn.scheme.LayeredSocketFactory#createSocket(java.net.Socket, java.lang.String, int,\n",[657,9463,9465],{"class":659,"line":9464},93,[657,9466,9467],{}," * boolean)\n",[657,9469,9471],{"class":659,"line":9470},94,[657,9472,9281],{},[657,9474,9476],{"class":659,"line":9475},95,[657,9477,9478],{}," public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException,\n",[657,9480,9482],{"class":659,"line":9481},96,[657,9483,9484],{}," UnknownHostException {\n",[657,9486,9488],{"class":659,"line":9487},97,[657,9489,9490],{}," return getSSLContext().getSocketFactory().createSocket(socket, host, port, autoClose);\n",[657,9492,9494],{"class":659,"line":9493},98,[657,9495,2188],{},[657,9497,9499],{"class":659,"line":9498},99,[657,9500,9501],{}," // -------------------------------------------------------------------\n",[657,9503,9505],{"class":659,"line":9504},100,[657,9506,9507],{}," // javadoc in org.apache.http.conn.scheme.SocketFactory says :\n",[657,9509,9511],{"class":659,"line":9510},101,[657,9512,9513],{}," // Both Object.equals() and Object.hashCode() must be overridden\n",[657,9515,9517],{"class":659,"line":9516},102,[657,9518,9519],{}," // for the correct operation of some connection managers\n",[657,9521,9523],{"class":659,"line":9522},103,[657,9524,9501],{},[657,9526,9528],{"class":659,"line":9527},104,[657,9529,9530],{}," public boolean equals(Object obj) {\n",[657,9532,9534],{"class":659,"line":9533},105,[657,9535,9536],{}," return ((obj != null) && obj.getClass().equals(EasySSLSocketFactory.class));\n",[657,9538,9540],{"class":659,"line":9539},106,[657,9541,2188],{},[657,9543,9545],{"class":659,"line":9544},107,[657,9546,9547],{}," public int hashCode() {\n",[657,9549,9551],{"class":659,"line":9550},108,[657,9552,9553],{}," return EasySSLSocketFactory.class.hashCode();\n",[657,9555,9557],{"class":659,"line":9556},109,[657,9558,2188],{},[657,9560,9562],{"class":659,"line":9561},110,[657,9563,2438],{},[19,9565,9566],{},"And the TrustManager:",[649,9568,9570],{"className":2130,"code":9569,"language":2132,"meta":110,"style":110},"\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",[460,9571,9572,9576,9580,9584,9588,9592,9596,9600,9604,9608,9612,9616,9620,9624,9628,9632,9636,9640,9644,9648,9653,9658,9663,9668,9673,9677,9682,9687,9691,9695,9700,9704,9708,9713,9718,9722,9727,9731,9736,9741,9746,9751,9756,9761,9766,9770,9775,9779,9783,9788,9792,9797,9802,9806,9810,9815,9819,9824,9829,9834,9838,9843,9847,9851,9855,9860,9864,9869,9874,9878],{"__ignoreMap":110},[657,9573,9574],{"class":659,"line":660},[657,9575,663],{"emptyLinePlaceholder":127},[657,9577,9578],{"class":659,"line":111},[657,9579,8999],{},[657,9581,9582],{"class":659,"line":671},[657,9583,9004],{},[657,9585,9586],{"class":659,"line":677},[657,9587,9009],{},[657,9589,9590],{"class":659,"line":683},[657,9591,9014],{},[657,9593,9594],{"class":659,"line":732},[657,9595,9019],{},[657,9597,9598],{"class":659,"line":738},[657,9599,9024],{},[657,9601,9602],{"class":659,"line":744},[657,9603,9029],{},[657,9605,9606],{"class":659,"line":750},[657,9607,9034],{},[657,9609,9610],{"class":659,"line":756},[657,9611,9039],{},[657,9613,9614],{"class":659,"line":907},[657,9615,9044],{},[657,9617,9618],{"class":659,"line":913},[657,9619,9039],{},[657,9621,9622],{"class":659,"line":919},[657,9623,9053],{},[657,9625,9626],{"class":659,"line":1006},[657,9627,9058],{},[657,9629,9630],{"class":659,"line":1088},[657,9631,9063],{},[657,9633,9634],{"class":659,"line":1094},[657,9635,9068],{},[657,9637,9638],{"class":659,"line":1100},[657,9639,9073],{},[657,9641,9642],{"class":659,"line":1106},[657,9643,9078],{},[657,9645,9646],{"class":659,"line":1112},[657,9647,9083],{},[657,9649,9650],{"class":659,"line":1118},[657,9651,9652],{},"import java.security.KeyStore;\n",[657,9654,9655],{"class":659,"line":1124},[657,9656,9657],{},"import java.security.KeyStoreException;\n",[657,9659,9660],{"class":659,"line":1130},[657,9661,9662],{},"import java.security.NoSuchAlgorithmException;\n",[657,9664,9665],{"class":659,"line":1136},[657,9666,9667],{},"import java.security.cert.CertificateException;\n",[657,9669,9670],{"class":659,"line":1142},[657,9671,9672],{},"import java.security.cert.X509Certificate;\n",[657,9674,9675],{"class":659,"line":1148},[657,9676,9123],{},[657,9678,9679],{"class":659,"line":1154},[657,9680,9681],{},"import javax.net.ssl.TrustManagerFactory;\n",[657,9683,9684],{"class":659,"line":1160},[657,9685,9686],{},"import javax.net.ssl.X509TrustManager;\n",[657,9688,9689],{"class":659,"line":1166},[657,9690,9153],{},[657,9692,9693],{"class":659,"line":1172},[657,9694,9167],{},[657,9696,9697],{"class":659,"line":1178},[657,9698,9699],{}," * @version $Id: EasyX509TrustManager.java 765355 2009-04-15 20:59:07Z evenisse $\n",[657,9701,9702],{"class":659,"line":1183},[657,9703,9177],{},[657,9705,9706],{"class":659,"line":1188},[657,9707,9083],{},[657,9709,9710],{"class":659,"line":1194},[657,9711,9712],{},"public class EasyX509TrustManager implements X509TrustManager {\n",[657,9714,9715],{"class":659,"line":1199},[657,9716,9717],{}," private X509TrustManager standardTrustManager = null;\n",[657,9719,9720],{"class":659,"line":1205},[657,9721,9266],{},[657,9723,9724],{"class":659,"line":1210},[657,9725,9726],{}," * Constructor for EasyX509TrustManager.\n",[657,9728,9729],{"class":659,"line":1215},[657,9730,9281],{},[657,9732,9733],{"class":659,"line":1220},[657,9734,9735],{}," public EasyX509TrustManager(KeyStore keystore) throws NoSuchAlgorithmException, KeyStoreException {\n",[657,9737,9738],{"class":659,"line":1225},[657,9739,9740],{}," super();\n",[657,9742,9743],{"class":659,"line":1230},[657,9744,9745],{}," TrustManagerFactory factory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());\n",[657,9747,9748],{"class":659,"line":1236},[657,9749,9750],{}," factory.init(keystore);\n",[657,9752,9753],{"class":659,"line":7370},[657,9754,9755],{}," TrustManager[] trustmanagers = factory.getTrustManagers();\n",[657,9757,9758],{"class":659,"line":7375},[657,9759,9760],{}," if (trustmanagers.length == 0) {\n",[657,9762,9763],{"class":659,"line":7390},[657,9764,9765],{}," throw new NoSuchAlgorithmException(\"no trust manager found\");\n",[657,9767,9768],{"class":659,"line":7406},[657,9769,2178],{},[657,9771,9772],{"class":659,"line":7420},[657,9773,9774],{}," this.standardTrustManager = (X509TrustManager) trustmanagers[0];\n",[657,9776,9777],{"class":659,"line":7425},[657,9778,2188],{},[657,9780,9781],{"class":659,"line":7440},[657,9782,9266],{},[657,9784,9785],{"class":659,"line":7456},[657,9786,9787],{}," * @see javax.net.ssl.X509TrustManager#checkClientTrusted(X509Certificate[],String authType)\n",[657,9789,9790],{"class":659,"line":7467},[657,9791,9281],{},[657,9793,9794],{"class":659,"line":7480},[657,9795,9796],{}," public void checkClientTrusted(X509Certificate[] certificates, String authType) throws CertificateException {\n",[657,9798,9799],{"class":659,"line":7493},[657,9800,9801],{}," standardTrustManager.checkClientTrusted(certificates, authType);\n",[657,9803,9804],{"class":659,"line":7508},[657,9805,2188],{},[657,9807,9808],{"class":659,"line":7522},[657,9809,9266],{},[657,9811,9812],{"class":659,"line":7527},[657,9813,9814],{}," * @see javax.net.ssl.X509TrustManager#checkServerTrusted(X509Certificate[],String authType)\n",[657,9816,9817],{"class":659,"line":7533},[657,9818,9281],{},[657,9820,9821],{"class":659,"line":7543},[657,9822,9823],{}," public void checkServerTrusted(X509Certificate[] certificates, String authType) throws CertificateException {\n",[657,9825,9826],{"class":659,"line":7554},[657,9827,9828],{}," if ((certificates != null) && (certificates.length == 1)) {\n",[657,9830,9831],{"class":659,"line":7567},[657,9832,9833],{}," certificates[0].checkValidity();\n",[657,9835,9836],{"class":659,"line":7580},[657,9837,3337],{},[657,9839,9840],{"class":659,"line":7593},[657,9841,9842],{}," standardTrustManager.checkServerTrusted(certificates, authType);\n",[657,9844,9845],{"class":659,"line":7607},[657,9846,2178],{},[657,9848,9849],{"class":659,"line":9294},[657,9850,2188],{},[657,9852,9853],{"class":659,"line":9300},[657,9854,9266],{},[657,9856,9857],{"class":659,"line":9306},[657,9858,9859],{}," * @see javax.net.ssl.X509TrustManager#getAcceptedIssuers()\n",[657,9861,9862],{"class":659,"line":9312},[657,9863,9281],{},[657,9865,9866],{"class":659,"line":9318},[657,9867,9868],{}," public X509Certificate[] getAcceptedIssuers() {\n",[657,9870,9871],{"class":659,"line":9324},[657,9872,9873],{}," return this.standardTrustManager.getAcceptedIssuers();\n",[657,9875,9876],{"class":659,"line":9330},[657,9877,2188],{},[657,9879,9880],{"class":659,"line":9336},[657,9881,2438],{},[19,9883,9884,9885,9890],{},"(Both classes are\nfrom ",[152,9886,9889],{"href":9887,"rel":9888},"https://web.archive.org/web/20111230175916/http://exchangeit.googlecode.com:80/svn-history/r23/trunk/src/com/byarger/exchangeit/",[156],"exchangeit","\nwith a small change on the EasySSLSocketFactory to work on android 2.2)",[19,9892,9893],{},"Now we have to do some other preparations and create a HttpClient that we can use to establish the connection:",[649,9895,9897],{"className":2130,"code":9896,"language":2132,"meta":110,"style":110},"\n//members\nprivate ClientConnectionManager clientConnectionManager;\nprivate HttpContext context;\nprivate HttpParams params;\n\n",[460,9898,9899,9903,9908,9913,9918],{"__ignoreMap":110},[657,9900,9901],{"class":659,"line":660},[657,9902,663],{"emptyLinePlaceholder":127},[657,9904,9905],{"class":659,"line":111},[657,9906,9907],{},"//members\n",[657,9909,9910],{"class":659,"line":671},[657,9911,9912],{},"private ClientConnectionManager clientConnectionManager;\n",[657,9914,9915],{"class":659,"line":677},[657,9916,9917],{},"private HttpContext context;\n",[657,9919,9920],{"class":659,"line":683},[657,9921,9922],{},"private HttpParams params;\n",[649,9924,9926],{"className":2130,"code":9925,"language":2132,"meta":110,"style":110},"\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",[460,9927,9928,9932,9937,9942,9947,9951,9956,9961,9966,9971,9976,9981,9986,9991,9996,10001,10006,10011,10016,10021,10026,10031,10036,10041,10046,10051,10056,10061],{"__ignoreMap":110},[657,9929,9930],{"class":659,"line":660},[657,9931,663],{"emptyLinePlaceholder":127},[657,9933,9934],{"class":659,"line":111},[657,9935,9936],{},"//constructor\n",[657,9938,9939],{"class":659,"line":671},[657,9940,9941],{},"public WebService(){\n",[657,9943,9944],{"class":659,"line":677},[657,9945,9946],{}," setup();\n",[657,9948,9949],{"class":659,"line":683},[657,9950,2438],{},[657,9952,9953],{"class":659,"line":732},[657,9954,9955],{},"//prepare for the https connection\n",[657,9957,9958],{"class":659,"line":738},[657,9959,9960],{},"//call this in the constructor of the class that does the connection if\n",[657,9962,9963],{"class":659,"line":744},[657,9964,9965],{},"//it's used multiple times\n",[657,9967,9968],{"class":659,"line":750},[657,9969,9970],{},"private void setup(){\n",[657,9972,9973],{"class":659,"line":756},[657,9974,9975],{},"SchemeRegistry schemeRegistry = new SchemeRegistry();\n",[657,9977,9978],{"class":659,"line":907},[657,9979,9980],{}," // http scheme\n",[657,9982,9983],{"class":659,"line":913},[657,9984,9985],{}," schemeRegistry.register(new Scheme(\"http\", PlainSocketFactory.getSocketFactory(), 80));\n",[657,9987,9988],{"class":659,"line":919},[657,9989,9990],{}," // https scheme\n",[657,9992,9993],{"class":659,"line":1006},[657,9994,9995],{}," schemeRegistry.register(new Scheme(\"https\", new EasySSLSocketFactory(), 443));\n",[657,9997,9998],{"class":659,"line":1088},[657,9999,10000],{}," params = new BasicHttpParams();\n",[657,10002,10003],{"class":659,"line":1094},[657,10004,10005],{}," params.setParameter(ConnManagerPNames.MAX_TOTAL_CONNECTIONS, 1);\n",[657,10007,10008],{"class":659,"line":1100},[657,10009,10010],{}," params.setParameter(ConnManagerPNames.MAX_CONNECTIONS_PER_ROUTE, new ConnPerRouteBean(1));\n",[657,10012,10013],{"class":659,"line":1106},[657,10014,10015],{}," params.setParameter(HttpProtocolParams.USE_EXPECT_CONTINUE, false);\n",[657,10017,10018],{"class":659,"line":1112},[657,10019,10020],{}," HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);\n",[657,10022,10023],{"class":659,"line":1118},[657,10024,10025],{}," HttpProtocolParams.setContentCharset(params, \"utf8\");\n",[657,10027,10028],{"class":659,"line":1124},[657,10029,10030],{}," CredentialsProvider credentialsProvider = new BasicCredentialsProvider();\n",[657,10032,10033],{"class":659,"line":1130},[657,10034,10035],{}," //set the user credentials for our site \"example.com\"\n",[657,10037,10038],{"class":659,"line":1136},[657,10039,10040],{}," credentialsProvider.setCredentials(new AuthScope(\"example.com\", AuthScope.ANY_PORT),\n",[657,10042,10043],{"class":659,"line":1142},[657,10044,10045],{}," new UsernamePasswordCredentials(\"UserNameHere\", \"UserPasswordHere\"));\n",[657,10047,10048],{"class":659,"line":1148},[657,10049,10050],{}," clientConnectionManager = new ThreadSafeClientConnManager(params, schemeRegistry);\n",[657,10052,10053],{"class":659,"line":1154},[657,10054,10055],{}," context = new BasicHttpContext();\n",[657,10057,10058],{"class":659,"line":1160},[657,10059,10060],{}," context.setAttribute(\"http.auth.credentials-provider\", credentialsProvider);\n",[657,10062,10063],{"class":659,"line":1166},[657,10064,2438],{},[649,10066,10068],{"className":2130,"code":10067,"language":2132,"meta":110,"style":110},"\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",[460,10069,10070,10074,10079,10084,10089,10094,10099,10104],{"__ignoreMap":110},[657,10071,10072],{"class":659,"line":660},[657,10073,663],{"emptyLinePlaceholder":127},[657,10075,10076],{"class":659,"line":111},[657,10077,10078],{},"public HttpResponse getResponseFromUrl(String url){\n",[657,10080,10081],{"class":659,"line":671},[657,10082,10083],{},"//connection (client has to be created for every new connection)\n",[657,10085,10086],{"class":659,"line":677},[657,10087,10088],{},"client = new DefaultHttpClient(clientConnectionManager, params);\n",[657,10090,10091],{"class":659,"line":683},[657,10092,10093],{},"HttpGet get = new HttpGet(url);\n",[657,10095,10096],{"class":659,"line":732},[657,10097,10098],{},"HttpResponse response = client.execute(get, context);\n",[657,10100,10101],{"class":659,"line":738},[657,10102,10103],{},"return response;\n",[657,10105,10106],{"class":659,"line":744},[657,10107,2438],{},[19,10109,10110],{},"And that's it. I hope this will help some of you to solve their problems with self-signed certs!",[1341,10112,2074],{},{"title":110,"searchDepth":111,"depth":111,"links":10114},[],[120,483],"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":8970,"description":8979},"blog/android-and-self-signed-ssl-certificates",[133,10124,10125,10126,10127,10128],"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":10132,"title":10133,"author":10134,"body":10135,"category":10755,"date":10756,"description":10757,"extension":124,"link":10758,"meta":10759,"navigation":127,"path":10760,"seo":10761,"slug":10139,"stem":10763,"tags":10764,"teaser":10769,"__hash__":10770},"blog/blog/routing-driving-directions-on-android-part-2-draw-the-route.md","Routing / Driving directions on Android – Part 2: Draw the route",[617],{"type":12,"value":10136,"toc":10750},[10137,10140,10149,10153,10156,10205,10208,10281,10284,10317,10321,10324,10327,10341,10344,10349,10356,10421,10424,10558,10562,10565,10568,10649,10652,10676,10679,10682,10685,10728,10731,10745,10748],[15,10138,10133],{"id":10139},"routing-driving-directions-on-android-part-2-draw-the-route",[19,10141,10142,10143,10148],{},"After you ",[152,10144,10147],{"href":10145,"rel":10146},"http://mobile.synyx.de/2010/06/routing-driving-directions-on-android-part-1-get-the-route/",[156],"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.",[23,10150,10152],{"id":10151},"create-a-suiting-overlay","Create a suiting Overlay",[19,10154,10155],{},"We basically need a Overlay that takes two Geopoints and maybe a color in which the lines should be drawn. So here We\nhave:",[649,10157,10159],{"className":2130,"code":10158,"language":2132,"meta":110,"style":110},"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",[460,10160,10161,10166,10171,10176,10181,10186,10191,10196,10201],{"__ignoreMap":110},[657,10162,10163],{"class":659,"line":660},[657,10164,10165],{},"public class RouteOverlay extends Overlay {\n",[657,10167,10168],{"class":659,"line":111},[657,10169,10170],{}," private GeoPoint gp1;\n",[657,10172,10173],{"class":659,"line":671},[657,10174,10175],{}," private GeoPoint gp2;\n",[657,10177,10178],{"class":659,"line":677},[657,10179,10180],{}," private int color;\n",[657,10182,10183],{"class":659,"line":683},[657,10184,10185],{},"public RouteOverlay(GeoPoint gp1, GeoPoint gp2, int color) {\n",[657,10187,10188],{"class":659,"line":732},[657,10189,10190],{}," this.gp1 = gp1;\n",[657,10192,10193],{"class":659,"line":738},[657,10194,10195],{}," this.gp2 = gp2;\n",[657,10197,10198],{"class":659,"line":744},[657,10199,10200],{}," this.color = color;\n",[657,10202,10203],{"class":659,"line":750},[657,10204,2188],{},[19,10206,10207],{},"Now all that’s left now for our Overlay is to override the draw() method and draw the line as we need it:",[649,10209,10211],{"className":2130,"code":10210,"language":2132,"meta":110,"style":110},"@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",[460,10212,10213,10217,10222,10227,10232,10237,10242,10247,10252,10257,10262,10267,10272,10277],{"__ignoreMap":110},[657,10214,10215],{"class":659,"line":660},[657,10216,2143],{},[657,10218,10219],{"class":659,"line":111},[657,10220,10221],{},"public void draw(Canvas canvas, MapView mapView, boolean shadow) {\n",[657,10223,10224],{"class":659,"line":671},[657,10225,10226],{}," Projection projection = mapView.getProjection();\n",[657,10228,10229],{"class":659,"line":677},[657,10230,10231],{}," Paint paint = new Paint();\n",[657,10233,10234],{"class":659,"line":683},[657,10235,10236],{}," Point point = new Point();\n",[657,10238,10239],{"class":659,"line":732},[657,10240,10241],{}," projection.toPixels(gp1, point);\n",[657,10243,10244],{"class":659,"line":738},[657,10245,10246],{}," paint.setColor(color);\n",[657,10248,10249],{"class":659,"line":744},[657,10250,10251],{}," Point point2 = new Point();\n",[657,10253,10254],{"class":659,"line":750},[657,10255,10256],{}," projection.toPixels(gp2, point2);\n",[657,10258,10259],{"class":659,"line":756},[657,10260,10261],{}," paint.setStrokeWidth(5);\n",[657,10263,10264],{"class":659,"line":907},[657,10265,10266],{}," paint.setAlpha(120);\n",[657,10268,10269],{"class":659,"line":913},[657,10270,10271],{}," canvas.drawLine(point.x, point.y, point2.x, point2.y, paint);\n",[657,10273,10274],{"class":659,"line":919},[657,10275,10276],{}," super.draw(canvas, mapView, shadow);\n",[657,10278,10279],{"class":659,"line":1006},[657,10280,2438],{},[19,10282,10283],{},"Back in the Activity, just iterate over the GeoPoints that you got from google maps and add each of them to the MapView:",[649,10285,10287],{"className":2130,"code":10286,"language":2132,"meta":110,"style":110},"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",[460,10288,10289,10294,10299,10304,10309,10313],{"__ignoreMap":110},[657,10290,10291],{"class":659,"line":660},[657,10292,10293],{},"private void drawPath(List geoPoints, int color) {\n",[657,10295,10296],{"class":659,"line":111},[657,10297,10298],{}," List overlays = mapView.getOverlays();\n",[657,10300,10301],{"class":659,"line":671},[657,10302,10303],{}," for (int i = 1; i \u003C geoPoints.size(); i++) {\n",[657,10305,10306],{"class":659,"line":677},[657,10307,10308],{}," overlays.add(new RouteOverlay(geoPoints.get(i - 1), geoPoints.get(i), color));\n",[657,10310,10311],{"class":659,"line":683},[657,10312,2640],{},[657,10314,10315],{"class":659,"line":732},[657,10316,2438],{},[23,10318,10320],{"id":10319},"get-location-updates-from-the-location-manager","Get location updates from the location manager",[19,10322,10323],{},"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!",[19,10325,10326],{},"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:",[649,10328,10330],{"className":2130,"code":10329,"language":2132,"meta":110,"style":110},"\nlocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 300000, 5000, this);\n\n",[460,10331,10332,10336],{"__ignoreMap":110},[657,10333,10334],{"class":659,"line":660},[657,10335,663],{"emptyLinePlaceholder":127},[657,10337,10338],{"class":659,"line":111},[657,10339,10340],{},"locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 300000, 5000, this);\n",[19,10342,10343],{},"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.",[19,10345,10346],{},[164,10347,10348],{},"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)",[19,10350,10351,10352,10355],{},"Also don’t forget to ",[164,10353,10354],{},"remove the listener"," if the MapView isn’t visible:",[649,10357,10359],{"className":2130,"code":10358,"language":2132,"meta":110,"style":110},"\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",[460,10360,10361,10365,10369,10374,10379,10384,10389,10393,10397,10402,10407,10412,10417],{"__ignoreMap":110},[657,10362,10363],{"class":659,"line":660},[657,10364,663],{"emptyLinePlaceholder":127},[657,10366,10367],{"class":659,"line":111},[657,10368,2143],{},[657,10370,10371],{"class":659,"line":671},[657,10372,10373],{}," protected void onPause() {\n",[657,10375,10376],{"class":659,"line":677},[657,10377,10378],{}," //remove the listener\n",[657,10380,10381],{"class":659,"line":683},[657,10382,10383],{}," locationManager.removeUpdates(this);\n",[657,10385,10386],{"class":659,"line":732},[657,10387,10388],{}," super.onPause();\n",[657,10390,10391],{"class":659,"line":738},[657,10392,2188],{},[657,10394,10395],{"class":659,"line":744},[657,10396,2143],{},[657,10398,10399],{"class":659,"line":750},[657,10400,10401],{}," protected void onResume() {\n",[657,10403,10404],{"class":659,"line":756},[657,10405,10406],{}," //add the listener again\n",[657,10408,10409],{"class":659,"line":907},[657,10410,10411],{}," locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 300000, 5000, this);\n",[657,10413,10414],{"class":659,"line":913},[657,10415,10416],{}," super.onResume();\n",[657,10418,10419],{"class":659,"line":919},[657,10420,2188],{},[19,10422,10423],{},"Now we have to let our MapActivity implement LocationListener and react to the updates:",[649,10425,10427],{"className":2130,"code":10426,"language":2132,"meta":110,"style":110},"\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",[460,10428,10429,10433,10438,10442,10447,10452,10456,10461,10466,10471,10476,10481,10486,10491,10496,10501,10506,10510,10515,10520,10525,10530,10535,10540,10544,10549,10554],{"__ignoreMap":110},[657,10430,10431],{"class":659,"line":660},[657,10432,663],{"emptyLinePlaceholder":127},[657,10434,10435],{"class":659,"line":111},[657,10436,10437],{},"public class RouteActivity extends MapActivity implements LocationListener {\n",[657,10439,10440],{"class":659,"line":671},[657,10441,2143],{},[657,10443,10444],{"class":659,"line":677},[657,10445,10446],{},"public void onLocationChanged(Location location) {\n",[657,10448,10449],{"class":659,"line":683},[657,10450,10451],{},"drawUserPosition(location);\n",[657,10453,10454],{"class":659,"line":732},[657,10455,2438],{},[657,10457,10458],{"class":659,"line":738},[657,10459,10460],{},"private void drawUserPosition(Location location) {\n",[657,10462,10463],{"class":659,"line":744},[657,10464,10465],{}," GeoPoint currentLocation;\n",[657,10467,10468],{"class":659,"line":750},[657,10469,10470],{}," currentLocation = new GeoPoint((int) ( location.getLatitude() * 1E6), (int) ( location\n",[657,10472,10473],{"class":659,"line":756},[657,10474,10475],{}," getLongitude() * 1E6));\n",[657,10477,10478],{"class":659,"line":907},[657,10479,10480],{}," OverlayItem currentLocationOverlay = new OverlayItem(currentLocation, getString(R.string.your_location),\n",[657,10482,10483],{"class":659,"line":913},[657,10484,10485],{}," getString(R.string.current_location));\n",[657,10487,10488],{"class":659,"line":919},[657,10489,10490],{}," mapOverlays.clear();\n",[657,10492,10493],{"class":659,"line":1006},[657,10494,10495],{}," if (locationOverlays.size() > 1) {\n",[657,10497,10498],{"class":659,"line":1088},[657,10499,10500],{}," // remove the old user position if there is one\n",[657,10502,10503],{"class":659,"line":1094},[657,10504,10505],{}," locationOverlays.removeOverlay(1);\n",[657,10507,10508],{"class":659,"line":1100},[657,10509,2640],{},[657,10511,10512],{"class":659,"line":1106},[657,10513,10514],{}," //add new user position\n",[657,10516,10517],{"class":659,"line":1112},[657,10518,10519],{}," locationOverlays.addOverlay(currentLocationOverlay, this.getResources().getDrawable(R.drawable.someImage));\n",[657,10521,10522],{"class":659,"line":1118},[657,10523,10524],{}," mapOverlays.add(locationOverlays);\n",[657,10526,10527],{"class":659,"line":1124},[657,10528,10529],{}," //.\n",[657,10531,10532],{"class":659,"line":1130},[657,10533,10534],{}," //. calculate / set the mapcenter, zoom to span\n",[657,10536,10537],{"class":659,"line":1136},[657,10538,10539],{}," //. see in previous posts\n",[657,10541,10542],{"class":659,"line":1142},[657,10543,10529],{},[657,10545,10546],{"class":659,"line":1148},[657,10547,10548],{}," RouteThread rt = new RouteThread(currentLocation, synyxGeoPoint, routeHandler);\n",[657,10550,10551],{"class":659,"line":1154},[657,10552,10553],{}," rt.start();\n",[657,10555,10556],{"class":659,"line":1160},[657,10557,2438],{},[23,10559,10561],{"id":10560},"make-it-threaded","Make it threaded",[19,10563,10564],{},"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.",[19,10566,10567],{},"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:",[649,10569,10571],{"className":2130,"code":10570,"language":2132,"meta":110,"style":110},"\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",[460,10572,10573,10577,10582,10587,10592,10597,10602,10607,10612,10617,10622,10627,10632,10637,10641,10645],{"__ignoreMap":110},[657,10574,10575],{"class":659,"line":660},[657,10576,663],{"emptyLinePlaceholder":127},[657,10578,10579],{"class":659,"line":111},[657,10580,10581],{},"private class RouteHandler extends Handler {\n",[657,10583,10584],{"class":659,"line":671},[657,10585,10586],{}," public void handleMessage(Message msg) {\n",[657,10588,10589],{"class":659,"line":677},[657,10590,10591],{}," boolean error = msg.getData().getBoolean(\"error\", false);\n",[657,10593,10594],{"class":659,"line":683},[657,10595,10596],{}," if (!error) {\n",[657,10598,10599],{"class":659,"line":732},[657,10600,10601],{}," // set the geopoints (we can't just add the overlays\n",[657,10603,10604],{"class":659,"line":738},[657,10605,10606],{}," // to the map here, because it's on a different thread\n",[657,10608,10609],{"class":659,"line":744},[657,10610,10611],{}," geoPoints = (List\u003CGeoPoint>) msg.obj;\n",[657,10613,10614],{"class":659,"line":750},[657,10615,10616],{}," post(updateRoute);\n",[657,10618,10619],{"class":659,"line":756},[657,10620,10621],{}," } else {\n",[657,10623,10624],{"class":659,"line":907},[657,10625,10626],{}," // maybe you want to show an error message here to\n",[657,10628,10629],{"class":659,"line":913},[657,10630,10631],{}," // notice the user that the route can not be displayed\n",[657,10633,10634],{"class":659,"line":919},[657,10635,10636],{}," // because there's no connection to the internet\n",[657,10638,10639],{"class":659,"line":1006},[657,10640,4255],{},[657,10642,10643],{"class":659,"line":1088},[657,10644,2178],{},[657,10646,10647],{"class":659,"line":1094},[657,10648,2188],{},[19,10650,10651],{},"Send the geopoints from the RouteThread:",[649,10653,10655],{"className":2130,"code":10654,"language":2132,"meta":110,"style":110},"\nMessage msg = new Message();\nmsg.obj = decodePoly(encoded);\nhandler.dispatchMessage(msg);\n\n",[460,10656,10657,10661,10666,10671],{"__ignoreMap":110},[657,10658,10659],{"class":659,"line":660},[657,10660,663],{"emptyLinePlaceholder":127},[657,10662,10663],{"class":659,"line":111},[657,10664,10665],{},"Message msg = new Message();\n",[657,10667,10668],{"class":659,"line":671},[657,10669,10670],{},"msg.obj = decodePoly(encoded);\n",[657,10672,10673],{"class":659,"line":677},[657,10674,10675],{},"handler.dispatchMessage(msg);\n",[19,10677,10678],{},"(If you have further data to send, use a Bundle object and add it to the Message.)",[19,10680,10681],{},"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.",[19,10683,10684],{},"First we create a runnable in the Activity:",[649,10686,10688],{"className":2130,"code":10687,"language":2132,"meta":110,"style":110},"\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",[460,10689,10690,10694,10699,10704,10709,10714,10719,10723],{"__ignoreMap":110},[657,10691,10692],{"class":659,"line":660},[657,10693,663],{"emptyLinePlaceholder":127},[657,10695,10696],{"class":659,"line":111},[657,10697,10698],{},"final Runnable updateRoute = new Runnable() {\n",[657,10700,10701],{"class":659,"line":671},[657,10702,10703],{}," public void run() {\n",[657,10705,10706],{"class":659,"line":677},[657,10707,10708],{}," // draw the path and then invalidate the mapview so that it redraws itself\n",[657,10710,10711],{"class":659,"line":683},[657,10712,10713],{}," drawPath(geoPoints, Color.GREEN);\n",[657,10715,10716],{"class":659,"line":732},[657,10717,10718],{}," mapView.invalidate();\n",[657,10720,10721],{"class":659,"line":738},[657,10722,2178],{},[657,10724,10725],{"class":659,"line":744},[657,10726,10727],{}," };\n",[19,10729,10730],{},"And all that is left to do is to call it from inside the handler:",[649,10732,10734],{"className":2130,"code":10733,"language":2132,"meta":110,"style":110},"\npost(updateRoute);\n\n",[460,10735,10736,10740],{"__ignoreMap":110},[657,10737,10738],{"class":659,"line":660},[657,10739,663],{"emptyLinePlaceholder":127},[657,10741,10742],{"class":659,"line":111},[657,10743,10744],{},"post(updateRoute);\n",[19,10746,10747],{},"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!",[1341,10749,2074],{},{"title":110,"searchDepth":111,"depth":111,"links":10751},[10752,10753,10754],{"id":10151,"depth":111,"text":10152},{"id":10319,"depth":111,"text":10320},{"id":10560,"depth":111,"text":10561},[120,483],"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":10133,"description":10762},"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",[133,10765,10766,10767,10768],"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":10772,"title":10773,"author":10774,"body":10775,"category":11254,"date":11255,"description":11256,"extension":124,"link":11257,"meta":11258,"navigation":127,"path":11259,"seo":11260,"slug":10779,"stem":11262,"tags":11263,"teaser":11265,"__hash__":11266},"blog/blog/routing-driving-directions-on-android-part-1-get-the-route.md","Routing / Driving directions on Android – Part 1: Get the route",[617],{"type":12,"value":10776,"toc":11245},[10777,10780,10795,10798,10802,10805,10808,10817,10820,10855,10858,10862,10866,10869,10875,10883,10886,10963,10966,11003,11006,11009,11018,11022,11025,11028,11067,11076,11220,11224,11227,11235,11239,11242],[15,10778,10773],{"id":10779},"routing-driving-directions-on-android-part-1-get-the-route",[19,10781,10782,10783,10788,10789,10794],{},"Complementary to Sebastian’s posts\nabout ",[152,10784,10787],{"href":10785,"rel":10786},"http://mobile.synyx.de/2010/04/google-maps-on-android/",[156],"how to navigate with the MapView","\nand ",[152,10790,10793],{"href":10791,"rel":10792},"http://mobile.synyx.de/2010/05/google-maps-on-android-part-2-overlays/",[156],"how to add customized overlays to it",", I\nwant to show you, how you display the route between two points.",[19,10796,10797],{},"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.",[23,10799,10801],{"id":10800},"getting-started","Getting started",[19,10803,10804],{},"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…”.",[19,10806,10807],{},"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:",[649,10809,10811],{"className":2130,"code":10810,"language":2132,"meta":110,"style":110},"synyxGeoPoint = new GeoPoint(49002175, 8394160);\n",[460,10812,10813],{"__ignoreMap":110},[657,10814,10815],{"class":659,"line":660},[657,10816,10810],{},[19,10818,10819],{},"And the user location is also quite easy to get:",[649,10821,10823],{"className":2130,"code":10822,"language":2132,"meta":110,"style":110},"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",[460,10824,10825,10830,10835,10840,10845,10850],{"__ignoreMap":110},[657,10826,10827],{"class":659,"line":660},[657,10828,10829],{},"locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);\n",[657,10831,10832],{"class":659,"line":111},[657,10833,10834],{},"Criteria criteria = new Criteria();\n",[657,10836,10837],{"class":659,"line":671},[657,10838,10839],{},"criteria.setAccuracy(Criteria.ACCURACY_FINE);\n",[657,10841,10842],{"class":659,"line":677},[657,10843,10844],{},"criteria.setAltitudeRequired(false);\n",[657,10846,10847],{"class":659,"line":683},[657,10848,10849],{},"Location lastKnownLocation =\n",[657,10851,10852],{"class":659,"line":732},[657,10853,10854],{},"locationManager.getLastKnownLocation(locationManager.getBestProvider(criteria, true));\n",[19,10856,10857],{},"With this we get the last known, accurate location of the user. So all there is left now, is to get the route.",[23,10859,10861],{"id":10860},"getting-the-geopoints-from-google-maps","Getting the geopoints from google maps",[1483,10863,10865],{"id":10864},"kml-with-driving-directions","Kml (with driving directions)",[19,10867,10868],{},"If you need the driving directions, you can build yourself a url like this one to get a kml file with all the\ninformation:",[19,10870,10871],{},[152,10872,10873],{"href":10873,"rel":10874},"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",[156],[19,10876,10877,10878,700],{},"(For the list of parameters of google maps, see\nhere: ",[152,10879,10882],{"href":10880,"rel":10881},"https://web.archive.org/web/20080901081831/http://mapki.com/wiki/Google_Map_Parameters",[156],"mapki.com",[19,10884,10885],{},"Here’s how we did it:",[649,10887,10889],{"className":2130,"code":10888,"language":2132,"meta":110,"style":110},"\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",[460,10890,10891,10895,10900,10905,10910,10915,10920,10925,10930,10935,10940,10944,10949,10954,10959],{"__ignoreMap":110},[657,10892,10893],{"class":659,"line":660},[657,10894,663],{"emptyLinePlaceholder":127},[657,10896,10897],{"class":659,"line":111},[657,10898,10899],{},"public String getUrl(GeoPoint src, GeoPoint dest){\n",[657,10901,10902],{"class":659,"line":671},[657,10903,10904],{},"StringBuilder urlString = new StringBuilder();\n",[657,10906,10907],{"class":659,"line":677},[657,10908,10909],{},"urlString.append(\"http://maps.google.com/maps?f=d&hl=en\");\n",[657,10911,10912],{"class":659,"line":683},[657,10913,10914],{},"urlString.append(\"&saddr=\");\n",[657,10916,10917],{"class":659,"line":732},[657,10918,10919],{},"urlString.append(Double.toString((double) src.getLatitudeE6() / 1.0E6));\n",[657,10921,10922],{"class":659,"line":738},[657,10923,10924],{},"urlString.append(\",\");\n",[657,10926,10927],{"class":659,"line":744},[657,10928,10929],{},"urlString.append(Double.toString((double) src.getLongitudeE6() / 1.0E6));\n",[657,10931,10932],{"class":659,"line":750},[657,10933,10934],{},"urlString.append(\"&daddr=\");// to\n",[657,10936,10937],{"class":659,"line":756},[657,10938,10939],{},"urlString.append(Double.toString((double) dest.getLatitudeE6() / 1.0E6));\n",[657,10941,10942],{"class":659,"line":907},[657,10943,10924],{},[657,10945,10946],{"class":659,"line":913},[657,10947,10948],{},"urlString.append(Double.toString((double) dest.getLongitudeE6() / 1.0E6));\n",[657,10950,10951],{"class":659,"line":919},[657,10952,10953],{},"urlString.append(\"&ie=UTF8&0&om=0&output=kml\");\n",[657,10955,10956],{"class":659,"line":1006},[657,10957,10958],{},"return urlString;\n",[657,10960,10961],{"class":659,"line":1088},[657,10962,2438],{},[19,10964,10965],{},"The file you get from this url looks like this:",[649,10967,10969],{"className":651,"code":10968,"language":653,"meta":110,"style":110},"\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",[460,10970,10971,10975,10980,10985,10990,10994,10999],{"__ignoreMap":110},[657,10972,10973],{"class":659,"line":660},[657,10974,663],{"emptyLinePlaceholder":127},[657,10976,10977],{"class":659,"line":111},[657,10978,10979],{},"Hohenheimer Str./B27 to Karlstraße/L561\n",[657,10981,10982],{"class":659,"line":671},[657,10983,10984],{},"....\n",[657,10986,10987],{"class":659,"line":677},[657,10988,10989],{},"Head southeast on Hohenheimer Str./B27 toward Bopserwaldstraße Continue to follow B27\n",[657,10991,10992],{"class":659,"line":683},[657,10993,10984],{},[657,10995,10996],{"class":659,"line":732},[657,10997,10998],{},"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",[657,11000,11001],{"class":659,"line":738},[657,11002,10984],{},[19,11004,11005],{},"(the first number of each pair is the longitude, the second the latitude)",[19,11007,11008],{},"To convert them to GeoPoints use:",[649,11010,11012],{"className":2130,"code":11011,"language":2132,"meta":110,"style":110},"GeoPoint geoPoint = new GeoPoint((int)(Double.parseDouble(latitude[0])*1E6),(int)(Double.parseDouble(longitude[0])*1E6));\n",[460,11013,11014],{"__ignoreMap":110},[657,11015,11016],{"class":659,"line":660},[657,11017,11011],{},[1483,11019,11021],{"id":11020},"json-without-driving-directions","JSON (without driving directions)",[19,11023,11024],{},"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.",[19,11026,11027],{},"again an example of what the server returns:",[649,11029,11031],{"className":4776,"code":11030,"language":4778,"meta":110,"style":110},"{tooltipHtml:\" (572x26#160;km / 5 hours 14 mins)\",polylines:[{id:\"route0\",points:\"se}bIgcwt@BSzA_D??Xh@dC|G??hDlIpBzFrAvC`@`BZjCV|@nApBtDvEx@rA| .....\n",[460,11032,11033],{"__ignoreMap":110},[657,11034,11035,11038,11041,11043,11046,11049,11052,11055,11058,11061,11064],{"class":659,"line":660},[657,11036,11037],{"class":796},"{",[657,11039,11040],{"class":2838},"tooltipHtml",[657,11042,6330],{"class":796},[657,11044,11045],{"class":2875},"\" (572x26#160;km / 5 hours 14 mins)\"",[657,11047,11048],{"class":796},",",[657,11050,11051],{"class":2838},"polylines",[657,11053,11054],{"class":796},":[{id:",[657,11056,11057],{"class":2875},"\"route0\"",[657,11059,11060],{"class":796},",points:",[657,11062,11063],{"class":2875},"\"se}bIgcwt@BSzA_D??Xh@dC|G??hDlIpBzFrAvC`@`BZjCV|@nApBtDvEx@rA| ....",[657,11065,11066],{"class":800},".\n",[19,11068,11069,11070,11075],{},"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 ",[152,11071,11074],{"href":11072,"rel":11073},"http://facstaff.unca.edu/mcmcclur/googlemaps/encodepolyline/",[156],"http://facstaff.unca.edu/",") :",[649,11077,11079],{"className":2130,"code":11078,"language":2132,"meta":110,"style":110},"// 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",[460,11080,11081,11086,11091,11096,11101,11106,11111,11116,11121,11126,11131,11136,11141,11146,11151,11156,11161,11166,11171,11176,11180,11184,11188,11192,11196,11201,11206,11211,11216],{"__ignoreMap":110},[657,11082,11083],{"class":659,"line":660},[657,11084,11085],{},"// get only the encoded geopoints\n",[657,11087,11088],{"class":659,"line":111},[657,11089,11090],{},"encoded = encoded.split(\"points:\"\")[1].split(\"\",\")[0];\n",[657,11092,11093],{"class":659,"line":671},[657,11094,11095],{},"// replace two backslashes by one (some error from the transmission)\n",[657,11097,11098],{"class":659,"line":677},[657,11099,11100],{},"encoded = encoded.replace(\"\\\\\", \"\\\");\n",[657,11102,11103],{"class":659,"line":683},[657,11104,11105],{},"//decoding\n",[657,11107,11108],{"class":659,"line":732},[657,11109,11110],{},"List poly = new ArrayList();\n",[657,11112,11113],{"class":659,"line":738},[657,11114,11115],{}," int index = 0, len = encoded.length();\n",[657,11117,11118],{"class":659,"line":744},[657,11119,11120],{}," int lat = 0, lng = 0;\n",[657,11122,11123],{"class":659,"line":750},[657,11124,11125],{}," while (index \u003C len) {\n",[657,11127,11128],{"class":659,"line":756},[657,11129,11130],{}," int b, shift = 0, result = 0;\n",[657,11132,11133],{"class":659,"line":907},[657,11134,11135],{}," do {\n",[657,11137,11138],{"class":659,"line":913},[657,11139,11140],{}," b = encoded.charAt(index++) - 63;\n",[657,11142,11143],{"class":659,"line":919},[657,11144,11145],{}," result |= (b & 0x1f) \u003C\u003C shift;\n",[657,11147,11148],{"class":659,"line":1006},[657,11149,11150],{}," shift += 5;\n",[657,11152,11153],{"class":659,"line":1088},[657,11154,11155],{}," } while (b >= 0x20);\n",[657,11157,11158],{"class":659,"line":1094},[657,11159,11160],{}," int dlat = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));\n",[657,11162,11163],{"class":659,"line":1100},[657,11164,11165],{}," lat += dlat;\n",[657,11167,11168],{"class":659,"line":1106},[657,11169,11170],{}," shift = 0;\n",[657,11172,11173],{"class":659,"line":1112},[657,11174,11175],{}," result = 0;\n",[657,11177,11178],{"class":659,"line":1118},[657,11179,11135],{},[657,11181,11182],{"class":659,"line":1124},[657,11183,11140],{},[657,11185,11186],{"class":659,"line":1130},[657,11187,11145],{},[657,11189,11190],{"class":659,"line":1136},[657,11191,11150],{},[657,11193,11194],{"class":659,"line":1142},[657,11195,11155],{},[657,11197,11198],{"class":659,"line":1148},[657,11199,11200],{}," int dlng = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));\n",[657,11202,11203],{"class":659,"line":1154},[657,11204,11205],{}," lng += dlng;\n",[657,11207,11208],{"class":659,"line":1160},[657,11209,11210],{}," GeoPoint p = new GeoPoint((int) (((double) lat / 1E5) * 1E6), (int) (((double) lng / 1E5) * 1E6));\n",[657,11212,11213],{"class":659,"line":1166},[657,11214,11215],{}," poly.add(p);\n",[657,11217,11218],{"class":659,"line":1172},[657,11219,2178],{},[23,11221,11223],{"id":11222},"getting-the-geopoints-from-open-streetmap","Getting the geopoints from open streetmap",[19,11225,11226],{},"You can also get the geopoints from open streetmap. It’s quite the same procedure, so i don’t write it all again.",[19,11228,11229,11230],{},"Here you can see for yourself: ",[152,11231,11234],{"href":11232,"rel":11233},"http://wiki.openstreetmap.org/wiki/YOURS#Routing_API",[156],"YOURS Routing_API",[23,11236,11238],{"id":11237},"whats-in-the-next-post","What’s in the next post",[19,11240,11241],{},"That’s it for today, in the next post i will show you how to draw the route on your MapView properly.",[1341,11243,11244],{},"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":110,"searchDepth":111,"depth":111,"links":11246},[11247,11248,11252,11253],{"id":10800,"depth":111,"text":10801},{"id":10860,"depth":111,"text":10861,"children":11249},[11250,11251],{"id":10864,"depth":671,"text":10865},{"id":11020,"depth":671,"text":11021},{"id":11222,"depth":111,"text":11223},{"id":11237,"depth":111,"text":11238},[120,483],"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":10773,"description":11261},"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",[133,11264,10766,10768],"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":11268,"title":11269,"author":11270,"body":11271,"category":11408,"date":11409,"description":11278,"extension":124,"link":11410,"meta":11411,"navigation":127,"path":11412,"seo":11413,"slug":11275,"stem":11414,"tags":11415,"teaser":11418,"__hash__":11419},"blog/blog/google-maps-on-maemo-5-part-1.md","Google Maps on Maemo 5 Part 1",[8494],{"type":12,"value":11272,"toc":11405},[11273,11276,11279,11283,11291,11294,11297,11300,11303,11306,11309,11312,11315,11318,11321,11324,11326,11329,11332,11334,11337,11340,11343,11346,11348,11351,11354,11357,11360,11362,11364,11367,11370,11372,11375,11378,11380,11383,11388,11391,11396,11399,11402],[15,11274,11269],{"id":11275},"google-maps-on-maemo-5-part-1",[19,11277,11278],{},"In this post i will show you how to realize a Maemo 5 Qt 4.6 application with google maps integration.",[23,11280,11282],{"id":11281},"the-map","The map:",[19,11284,11285,11286,11290],{},"There is a good short tutorial with included source code: ",[152,11287,11288],{"href":11288,"rel":11289},"http://efforts.embedded.ufcg.edu.br/qt/?p=80",[156],". 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.",[19,11292,11293],{},"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?",[19,11295,11296],{},"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.",[19,11298,11299],{},"Sourcecode of index.html with javascript methods for google’s api:",[19,11301,11302],{},"`function initialize(){",[19,11304,11305],{},"map = new GMap2(document.getElementById(\"map\"));",[19,11307,11308],{},"map.setCenter( new GLatLng(49.002397,8.394251),10 );",[19,11310,11311],{},"var point = new GLatLng(49.002397,8.394251);",[19,11313,11314],{},"map.addOverlay(new GMarker(point));",[19,11316,11317],{},"openSynyx();",[19,11319,11320],{},"}`",[19,11322,11323],{},"`function openSynyx()",[19,11325,11037],{},[19,11327,11328],{},"map.setCenter( new GLatLng(49.002397,8.394251),15 );",[19,11330,11331],{},"map.openInfoWindow(map.getCenter(),document.createTextNode(\"Synyx GmbH & Co. KG\"));",[19,11333,11320],{},[19,11335,11336],{},"`function route(from){",[19,11338,11339],{},"map.clearOverlays();",[19,11341,11342],{},"directions = new GDirections(map);",[19,11344,11345],{},"directions.load(\"from: \"+from+\" to: Karlsruhe, Karlstrasse 68\");",[19,11347,11320],{},[19,11349,11350],{},"`",[3008,11352],{"id":10766,"style":11353},"width: 450px; height: 400px",[19,11355,11356],{},"The map.cpp acts is a Children of QWebView and acts as poxy to the javascript methods:",[19,11358,11359],{},"`Map::Map(QWidget *parent) : QWebView(parent)",[19,11361,11037],{},[19,11363,11320],{},[19,11365,11366],{},"`void Map::naviFrom(QString from){",[19,11368,11369],{},"this->page()->mainFrame()->evaluateJavaScript(QString(\"route(\"%1\")\").arg(from));",[19,11371,11320],{},[19,11373,11374],{},"`void Map::openSynyx(){",[19,11376,11377],{},"this->page()->mainFrame()->evaluateJavaScript(\"openSynyx()\");",[19,11379,11320],{},[19,11381,11382],{},"mainscreen.cpp initializes the map with the path to the html file",[19,11384,11385],{},[460,11386,11387],{},"map->load(QUrl(\"./index.html\") ) ;",[19,11389,11390],{},"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:",[19,11392,11393],{},[33,11394],{"alt":110,"src":11395},"https://media.synyx.de/uploads//2010/06/maps_navi.png",[19,11397,11398],{},"google maps in maemo 5",[19,11400,11401],{},"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.",[19,11403,11404],{},"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":110,"searchDepth":111,"depth":111,"links":11406},[11407],{"id":11281,"depth":111,"text":11282},[120,483],"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":11269,"description":11278},"blog/google-maps-on-maemo-5-part-1",[11416,8609,11417],"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":11421,"title":5067,"author":11422,"body":11423,"category":11457,"date":11458,"description":11459,"extension":124,"link":11460,"meta":11461,"navigation":127,"path":11462,"seo":11463,"slug":11427,"stem":11465,"tags":11466,"teaser":11467,"__hash__":11468},"blog/blog/wwdc-2010.md",[5023],{"type":12,"value":11424,"toc":11455},[11425,11428,11437,11452],[15,11426,5067],{"id":11427},"wwdc-2010",[19,11429,11430,11431,11436],{},"I’m going to ",[152,11432,11435],{"href":11433,"rel":11434},"http://developer.apple.com/wwdc/",[156],"Apple’s WWDC"," this year and I’m pretty excited to fully dive into iPhone\ndevelopment for the whole next week. That it takes place in San Francisco is a nice bonus, as well.",[19,11438,11439,11440,11445,11446,11451],{},"The conference is ",[152,11441,11444],{"href":11442,"rel":11443},"http://developer.apple.com/wwdc/sessions/",[156],"mostly covering iPhone"," related topics. There\nare ",[152,11447,11450],{"href":11448,"rel":11449},"http://www.fscklog.com/2010/05/apple-best%C3%A4tigt-wwdc-keynote-mit-steve-jobs-am-7-juni.html",[156],"rumors"," that Steven Jobs\nwill present the new iPhone on Monday’s keynote. I’ll make sure I won’t miss that. My schedule for the week is already\nplanned with topics like game development, multitasking and user interface design.",[19,11453,11454],{},"If you are going to WWDC as well and would like to meet up, let me know.",{"title":110,"searchDepth":111,"depth":111,"links":11456},[],[120],"2010-06-03T15:12:08","I’m going to Apple’s WWDC this year and I’m pretty excited to fully dive into iPhone\\ndevelopment for the whole next week. That it takes place in San Francisco is a nice bonus, as well.","https://synyx.de/blog/wwdc-2010/",{},"/blog/wwdc-2010",{"title":5067,"description":11464},"I’m going to Apple’s WWDC this year and I’m pretty excited to fully dive into iPhone\ndevelopment for the whole next week. That it takes place in San Francisco is a nice bonus, as well.","blog/wwdc-2010",[5046,5016,5047],"I’m going to Apple’s WWDC this year and I’m pretty excited to fully dive into iPhone development for the whole next week. That it takes place in San Francisco is…","0PlPIuqAd36_OiWhK21AO20tc4c6Y1ucyQYMRpnc0aQ",{"id":11470,"title":11471,"author":11472,"body":11473,"category":11553,"date":11554,"description":110,"extension":124,"link":11555,"meta":11556,"navigation":127,"path":11557,"seo":11558,"slug":11559,"stem":11560,"tags":11561,"teaser":11562,"__hash__":11563},"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",[8494],{"type":12,"value":11474,"toc":11546},[11475,11478,11482,11491,11494,11499,11502,11506,11509,11515,11518,11521,11525,11528,11532,11539,11543],[15,11476,11471],{"id":11477},"howto-startup-with-maemo-and-qt-46-step2-configure-your-ide",[23,11479,11481],{"id":11480},"configuration-of-qt","Configuration of Qt:",[19,11483,11484,11485,11490],{},"My ",[152,11486,11489],{"href":11487,"rel":11488},"http://mobile.synyx.de/2010/04/howto-startup-with-maemo-and-qt-4-6/",[156],"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.",[19,11492,11493],{},"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.",[19,11495,11496],{},[33,11497],{"alt":110,"src":11498},"https://media.synyx.de/uploads//2010/06/setupqtCreator1.png",[19,11500,11501],{},"configure your QT version for Eclipse",[23,11503,11505],{"id":11504},"check-the-installed-targets","Check the installed targets:",[19,11507,11508],{},"Goto Windows->Preferences->Maemo->Installed Targets. You should see an entry “scratchbox 1” with two subentries(\ntargets).",[19,11510,11511],{},[33,11512],{"alt":11513,"src":11514},"\"your configuration should look like this\"","https://media.synyx.de/uploads//2010/06/setupScratchbox.png",[19,11516,11517],{},"your configuration should look like this",[19,11519,11520],{},"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.",[23,11522,11524],{"id":11523},"create-a-new-project","Create a new project:",[19,11526,11527],{},"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.",[23,11529,11531],{"id":11530},"correct-the-qt-paths","Correct the Qt paths:",[19,11533,11534,11535,11538],{},"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 ../",[164,11536,11537],{},"qt","/Qt…\nfrom the path.",[23,11540,11542],{"id":11541},"congratulation","Congratulation:",[19,11544,11545],{},"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":110,"searchDepth":111,"depth":111,"links":11547},[11548,11549,11550,11551,11552],{"id":11480,"depth":111,"text":11481},{"id":11504,"depth":111,"text":11505},{"id":11523,"depth":111,"text":11524},{"id":11530,"depth":111,"text":11531},{"id":11541,"depth":111,"text":11542},[120,483],"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":11471,"description":110},"howto-startup-with-maemo-and-qt-4-6-step2-configure-ide","blog/howto-startup-with-maemo-and-qt-4-6-step2-configure-ide",[8609,11417],"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":11565,"title":11566,"author":11567,"body":11568,"category":11804,"date":11805,"description":11806,"extension":124,"link":11807,"meta":11808,"navigation":127,"path":11809,"seo":11810,"slug":11572,"stem":11812,"tags":11813,"teaser":11815,"__hash__":11816},"blog/blog/performance-optimization-in-synyx-sudoku.md","Performance optimization in Synyx Sudoku",[617],{"type":12,"value":11569,"toc":11802},[11570,11573,11582,11585,11588,11591,11594,11603,11606,11609,11612,11655,11658,11769,11772,11787,11794,11797,11800],[15,11571,11566],{"id":11572},"performance-optimization-in-synyx-sudoku",[19,11574,11575,11576,11581],{},"Now that ",[152,11577,11580],{"href":11578,"rel":11579},"http://mobile.synyx.de/2010/04/22/release-of-synyxsudoku",[156],"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.",[19,11583,11584],{},"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.",[19,11586,11587],{},"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.",[19,11589,11590],{},"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.",[19,11592,11593],{},"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.",[19,11595,11596,11597,11602],{},"After a little bit of research we found the solution to this in the google I/O\nvideo ",[152,11598,11601],{"href":11599,"rel":11600},"http://www.youtube.com/watch?v=N6YdwzAvwOA",[156],"“Make your Android UI Fast and Efficient”",", which you really should\nwatch, if you haven’t by now!",[19,11604,11605],{},"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.",[19,11607,11608],{},"And that’s how it’s looking in the code:",[19,11610,11611],{},"First you have to overwrite the BaseAdapter from android so that you can give it your specific views to display:",[649,11613,11615],{"className":2130,"code":11614,"language":2132,"meta":110,"style":110},"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",[460,11616,11617,11622,11627,11632,11637,11642,11647,11651],{"__ignoreMap":110},[657,11618,11619],{"class":659,"line":660},[657,11620,11621],{},"public class HighscoreListAdapter extends BaseAdapter {\n",[657,11623,11624],{"class":659,"line":111},[657,11625,11626],{}," private List valueList;\n",[657,11628,11629],{"class":659,"line":671},[657,11630,11631],{}," private LayoutInflater inflater;\n",[657,11633,11634],{"class":659,"line":677},[657,11635,11636],{}," public HighscoreListAdapter(List highscoreValueList, LayoutInflater inflater) {\n",[657,11638,11639],{"class":659,"line":683},[657,11640,11641],{}," this.inflater = inflater;\n",[657,11643,11644],{"class":659,"line":732},[657,11645,11646],{}," this.valueList = highscoreValueList;\n",[657,11648,11649],{"class":659,"line":738},[657,11650,8846],{},[657,11652,11653],{"class":659,"line":744},[657,11654,2438],{},[19,11656,11657],{},"and also overwrite the getView method from it, so that you can make your recycling in there:",[649,11659,11661],{"className":2130,"code":11660,"language":2132,"meta":110,"style":110},"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",[460,11662,11663,11667,11672,11677,11682,11687,11692,11697,11702,11707,11712,11717,11721,11726,11731,11735,11740,11745,11750,11755,11760,11765],{"__ignoreMap":110},[657,11664,11665],{"class":659,"line":660},[657,11666,4561],{},[657,11668,11669],{"class":659,"line":111},[657,11670,11671],{}," HighscoreViewHolder highscoreViewHolder;\n",[657,11673,11674],{"class":659,"line":671},[657,11675,11676],{}," if (convertView == null) {\n",[657,11678,11679],{"class":659,"line":677},[657,11680,11681],{}," // the first few elements of the list are created out of the xml\n",[657,11683,11684],{"class":659,"line":683},[657,11685,11686],{}," convertView = inflater.inflate(R.layout.highscore_list_entry, null);\n",[657,11688,11689],{"class":659,"line":732},[657,11690,11691],{}," highscoreViewHolder = new HighscoreViewHolder();\n",[657,11693,11694],{"class":659,"line":738},[657,11695,11696],{}," highscoreViewHolder.name = (TextView) convertView.findViewById(R.id.highscore_list_entry_name);\n",[657,11698,11699],{"class":659,"line":744},[657,11700,11701],{}," highscoreViewHolder.score = (TextView) convertView.findViewById(R.id.highscore_list_entry_score);\n",[657,11703,11704],{"class":659,"line":750},[657,11705,11706],{}," highscoreViewHolder.rank = (TextView) convertView.findViewById(R.id.highscore_list_entry_rank);\n",[657,11708,11709],{"class":659,"line":756},[657,11710,11711],{}," convertView.setTag(highscoreViewHolder);\n",[657,11713,11714],{"class":659,"line":907},[657,11715,11716],{}," convertView.setFocusable(false);\n",[657,11718,11719],{"class":659,"line":913},[657,11720,2621],{},[657,11722,11723],{"class":659,"line":919},[657,11724,11725],{}," // recycle of the view that went out of the view\n",[657,11727,11728],{"class":659,"line":1006},[657,11729,11730],{}," highscoreViewHolder = (HighscoreViewHolder) convertView.getTag();\n",[657,11732,11733],{"class":659,"line":1088},[657,11734,8846],{},[657,11736,11737],{"class":659,"line":1094},[657,11738,11739],{}," HighscoreValue value = this.valueList.get(position);\n",[657,11741,11742],{"class":659,"line":1100},[657,11743,11744],{}," // set the new values\n",[657,11746,11747],{"class":659,"line":1106},[657,11748,11749],{}," highscoreViewHolder.name.setText(value.getName());\n",[657,11751,11752],{"class":659,"line":1112},[657,11753,11754],{}," highscoreViewHolder.score.setText(Integer.toString(value.getScore()));\n",[657,11756,11757],{"class":659,"line":1118},[657,11758,11759],{}," highscoreViewHolder.rank.setText(Integer.toString(value.getRank()));\n",[657,11761,11762],{"class":659,"line":1124},[657,11763,11764],{}," return convertView;\n",[657,11766,11767],{"class":659,"line":1130},[657,11768,2438],{},[19,11770,11771],{},"now whats only left is to create the adapter, and assign it to the ListView:",[649,11773,11775],{"className":2130,"code":11774,"language":2132,"meta":110,"style":110},"highscoreListAdapter = new HighscoreListAdapter(highscoreList, getLayoutInflater());\nlistView.setAdapter(highscoreListAdapter);\n",[460,11776,11777,11782],{"__ignoreMap":110},[657,11778,11779],{"class":659,"line":660},[657,11780,11781],{},"highscoreListAdapter = new HighscoreListAdapter(highscoreList, getLayoutInflater());\n",[657,11783,11784],{"class":659,"line":111},[657,11785,11786],{},"listView.setAdapter(highscoreListAdapter);\n",[19,11788,11789,11790,11793],{},"If you make some changes in the data set you gave to the adapter, just call ",[693,11791,11792],{},"notifyDataSetChanged()"," on it to let it\nrefresh itself.",[19,11795,11796],{},"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.",[19,11798,11799],{},"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.",[1341,11801,2074],{},{"title":110,"searchDepth":111,"depth":111,"links":11803},[],[120,483],"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":11566,"description":11811},"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",[133,11814],"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":11818,"title":11819,"author":11820,"body":11822,"category":11942,"date":11943,"description":11944,"extension":124,"link":11945,"meta":11946,"navigation":127,"path":11947,"seo":11948,"slug":11826,"stem":11949,"tags":11950,"teaser":11952,"__hash__":11953},"blog/blog/in-my-humble-opinion-froyo-rocks.md","In my humble opinion — FroYo rocks!",[11821],"arrasz",{"type":12,"value":11823,"toc":11940},[11824,11827,11830,11833,11838,11841,11849,11852,11860,11865,11868,11876,11878,11904,11909,11920,11923,11928,11931,11934],[15,11825,11819],{"id":11826},"in-my-humble-opinion-froyo-rocks",[19,11828,11829],{},"FroYo (Frozen Yoghurt) is the name Google gave its new Android 2.2 Release. FroYo is like each previous version a\nmixture between API Changes, new Userfeatures and some new cool Apps.",[19,11831,11832],{},"So, let’s divide this review into theese several specific parts and some addons at the end:",[19,11834,11835],{},[164,11836,11837],{},"First, the imho, absolutely most important part, the Enterprise Features:",[19,11839,11840],{},"New API-Features:",[85,11842,11843,11846],{},[88,11844,11845],{},"Data Backup API",[88,11847,11848],{},"Possibility to save passwords secure",[19,11850,11851],{},"New User Features:",[85,11853,11854,11857],{},[88,11855,11856],{},"updated Exchange Features",[88,11858,11859],{},"Remote Wipe",[19,11861,11862],{},[164,11863,11864],{},"Furthermore there are, of course, some more, not so enterprisy features 😉 :",[19,11866,11867],{},"New Apps:",[85,11869,11870,11873],{},[88,11871,11872],{},"Camera and Camcorder updated (possible to enable manually the leds for usage within camcorder)",[88,11874,11875],{},"Android Tethering and Usage as Hotspot",[19,11877,11851],{},[85,11879,11880,11883,11886,11889,11892,11895,11898,11901],{},[88,11881,11882],{},"Flashsupport , Airsupport (useful?)",[88,11884,11885],{},"Possibility to save Apps on SD-Card",[88,11887,11888],{},"JIT Compiler (hey this improves performance 3-5 times!!!)",[88,11890,11891],{},"HTML5 compatible browser?",[88,11893,11894],{},"With FroYo, users will be able to sync their local music collection with their Android device and stream wirelessly.",[88,11896,11897],{},"Users will also be available to backup their Apps in the Cloud",[88,11899,11900],{},"Android 2.2 finally supports multi-language keyboards too!",[88,11902,11903],{},"a lot of cool Marketplace updates (Webmarket like Androidpit.de and Search and Categories! and One click to update\nall, allow automatic update!!!! )",[19,11905,11906],{},[164,11907,11908],{},"And now some more rumors on what helps FroYo being the Apple poison 😉 :",[85,11910,11911,11914,11917],{},[88,11912,11913],{},"Kernel 2.6.32 will also improve speed for snapdragon processor smartphones like the Desire or the Milestone",[88,11915,11916],{},"Developers will be able to implement Services which interact with kind of Push Notifications. The killer here is that\nthis works bidirectional so that apps can also notifiy of feed Webapps in the Cloud with data! Interesting could be if\nGoogle is here open minded enough not to chain this, imho killerfeature, to Chrome.",[88,11918,11919],{},"Browserapps are able to use camera or sensors directly via JS! Is this the end of Tools like PhoneGap?",[19,11921,11922],{},"For those who like it … the new Google Buzz app should be much better than the crappy web version.",[85,11924,11925],{},[88,11926,11927],{},"Rumors told also that the new market is able to keep parts of froyo OS up to date automatically… could help to reduce\ndevice fragmentation!!!",[19,11929,11930],{},"As you can see, the list of new improvements is very long and in my opinion this is only the start of a little\nrevolution. All theese new features will enable Android Smartphone for a wide variety of really cool apps which we\nactually never thaught about because it was just not possible to implement ’em… we will dig more into Froyo if the first\nupdated Smartphone is available for us at Synyx… so stay tuned…",[19,11932,11933],{},"As addon, today the first Nexus One users told about their experience after the update… read more",[19,11935,11936],{},[152,11937,11938],{"href":11938,"rel":11939},"http://forum.xda-developers.com/showthread.php?t=686591",[156],{"title":110,"searchDepth":111,"depth":111,"links":11941},[],[120],"2010-05-26T11:39:24","FroYo (Frozen Yoghurt) is the name Google gave its new Android 2.2 Release. FroYo is like each previous version a\\nmixture between API Changes, new Userfeatures and some new cool Apps.","https://synyx.de/blog/in-my-humble-opinion-froyo-rocks/",{},"/blog/in-my-humble-opinion-froyo-rocks",{"title":11819,"description":11829},"blog/in-my-humble-opinion-froyo-rocks",[133,11951,2132],"froyo","FroYo (Frozen Yoghurt) is the name Google gave its new Android 2.2 Release. FroYo is like each previous version a mixture between API Changes, new Userfeatures and some new cool…","pNHe7FxDSHWPlTA-DybrIz7fTVlnHhoV7GHkOZCAr2I",{"id":11955,"title":11956,"author":11957,"body":11958,"category":12245,"date":12246,"description":12247,"extension":124,"link":12248,"meta":12249,"navigation":127,"path":12250,"seo":12251,"slug":11962,"stem":12253,"tags":12254,"teaser":12255,"__hash__":12256},"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",[5023],{"type":12,"value":11959,"toc":12243},[11960,11963,11976,11979,11985,11988,12004,12170,12173,12178,12201,12208,12222,12229,12234,12241],[15,11961,11956],{"id":11962},"how-to-add-a-find-your-company-feature-to-your-iphone-app-part-ii",[19,11964,11965,11966,11971,11972,5074],{},"In\nmy ",[152,11967,11970],{"href":11968,"rel":11969},"http://mobile.synyx.de/2010/05/06/how-to-add-a-find-your-company-feature-to-your-iphone-app-part-i/",[156],"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 ",[152,11973,8949],{"href":11974,"rel":11975},"http://gist.github.com/388323/ea35c6d31270babb73ec052f1442c43afd6b5510",[156],[19,11977,11978],{},"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:",[19,11980,11981],{},[33,11982],{"alt":11983,"src":11984},"\"screen_synyx_map\"","https://media.synyx.de/uploads//2010/05/maps-3.png",[19,11986,11987],{},"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.",[19,11989,11990,11991,11994,11995,11999,12000,12003],{},"That’s exactly what the method ",[693,11992,11993],{},"centerMapAroundAnnotations"," at\nline ",[152,11996,11998],{"href":11974,"rel":11997},[156],"108"," of our ",[693,12001,12002],{},"UIViewController"," does:",[649,12005,12007],{"className":6348,"code":12006,"language":6350,"meta":110,"style":110},"\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",[460,12008,12009,12013,12018,12022,12027,12032,12037,12042,12047,12052,12057,12062,12066,12071,12076,12081,12086,12090,12095,12100,12105,12110,12115,12120,12125,12130,12135,12140,12145,12150,12155,12160,12165],{"__ignoreMap":110},[657,12010,12011],{"class":659,"line":660},[657,12012,663],{"emptyLinePlaceholder":127},[657,12014,12015],{"class":659,"line":111},[657,12016,12017],{},"if ( [[self.mapView annotations] count] \u003C 2 )\n",[657,12019,12020],{"class":659,"line":671},[657,12021,8889],{},[657,12023,12024],{"class":659,"line":677},[657,12025,12026],{},"CLLocationCoordinate2D min;\n",[657,12028,12029],{"class":659,"line":683},[657,12030,12031],{},"CLLocationCoordinate2D max;\n",[657,12033,12034],{"class":659,"line":732},[657,12035,12036],{},"BOOL minMaxInitialized = NO;\n",[657,12038,12039],{"class":659,"line":738},[657,12040,12041],{},"for ( id\u003CMKAnnotation> a in [self.mapView annotations] ) {\n",[657,12043,12044],{"class":659,"line":744},[657,12045,12046],{}," if ( !minMaxInitialized ) {\n",[657,12048,12049],{"class":659,"line":750},[657,12050,12051],{}," min = a.coordinate;\n",[657,12053,12054],{"class":659,"line":756},[657,12055,12056],{}," max = a.coordinate;\n",[657,12058,12059],{"class":659,"line":907},[657,12060,12061],{}," minMaxInitialized = YES;\n",[657,12063,12064],{"class":659,"line":913},[657,12065,2621],{},[657,12067,12068],{"class":659,"line":919},[657,12069,12070],{}," min.latitude = MIN( min.latitude, a.coordinate.latitude );\n",[657,12072,12073],{"class":659,"line":1006},[657,12074,12075],{}," min.longitude = MIN( min.longitude, a.coordinate.longitude );\n",[657,12077,12078],{"class":659,"line":1088},[657,12079,12080],{}," max.latitude = MAX( max.latitude, a.coordinate.latitude );\n",[657,12082,12083],{"class":659,"line":1094},[657,12084,12085],{}," max.longitude = MAX( max.longitude, a.coordinate.longitude );\n",[657,12087,12088],{"class":659,"line":1100},[657,12089,8846],{},[657,12091,12092],{"class":659,"line":1106},[657,12093,12094],{},"}\u003Cbr/>\n",[657,12096,12097],{"class":659,"line":1112},[657,12098,12099],{},"CLLocation* locSouthWest = [[CLLocation alloc] initWithLatitude: min.latitude longitude: min.longitude];\n",[657,12101,12102],{"class":659,"line":1118},[657,12103,12104],{},"CLLocation* locSouthEast = [[CLLocation alloc] initWithLatitude: min.latitude longitude: max.longitude];\n",[657,12106,12107],{"class":659,"line":1124},[657,12108,12109],{},"CLLocation* locNorthEast = [[CLLocation alloc] initWithLatitude: max.latitude longitude: max.longitude];\n",[657,12111,12112],{"class":659,"line":1130},[657,12113,12114],{},"CLLocationCoordinate2D regionCenter;\n",[657,12116,12117],{"class":659,"line":1136},[657,12118,12119],{},"regionCenter.latitude = (min.latitude + max.latitude) / 2.0;\n",[657,12121,12122],{"class":659,"line":1142},[657,12123,12124],{},"regionCenter.longitude = (min.longitude + max.longitude) / 2.0;\u003Cbr/>\n",[657,12126,12127],{"class":659,"line":1148},[657,12128,12129],{},"CLLocationDistance latMeters = [locSouthEast getDistanceFrom: locNorthEast];\n",[657,12131,12132],{"class":659,"line":1154},[657,12133,12134],{},"CLLocationDistance lonMeters = [locSouthEast getDistanceFrom: locSouthWest];\n",[657,12136,12137],{"class":659,"line":1160},[657,12138,12139],{},"MKCoordinateRegion region;\n",[657,12141,12142],{"class":659,"line":1166},[657,12143,12144],{},"region = MKCoordinateRegionMakeWithDistance( regionCenter, latMeters, lonMeters );\n",[657,12146,12147],{"class":659,"line":1172},[657,12148,12149],{},"MKCoordinateRegion fitRegion = [self.mapView regionThatFits: region];\n",[657,12151,12152],{"class":659,"line":1178},[657,12153,12154],{},"[self.mapView setRegion: fitRegion animated: YES];\n",[657,12156,12157],{"class":659,"line":1183},[657,12158,12159],{},"[locSouthWest release];\n",[657,12161,12162],{"class":659,"line":1188},[657,12163,12164],{},"[locSouthEast release];\n",[657,12166,12167],{"class":659,"line":1194},[657,12168,12169],{},"[locNorthEast release];\n",[19,12171,12172],{},"The code might look complicated, but we can break it down into 3 steps:",[519,12174,12175],{},[88,12176,12177],{},"Iterate over all our custom annotations (we only have one) to determine the max/min latitude and longitude",[85,12179,12180,12194],{},[88,12181,12182,12183,4799,12186,12189,12190,12193],{},"This results in a triangle ",[693,12184,12185],{},"locSouthWest",[693,12187,12188],{},"locSouthEast"," and ",[693,12191,12192],{},"locNorthEast",", that we can use to determine the center\nfor our zoom",[88,12195,12196,12197,12200],{},"Using the distance between the triangle points and the center, we can fit the map into a ",[693,12198,12199],{},"MKCoordinateRegion",", that\nwill zoom it so that everything fits on the screen.",[19,12202,12203,12204,12207],{},"One thing this algorithm doesn’t include, is the ",[693,12205,12206],{},"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:",[649,12209,12211],{"className":6348,"code":12210,"language":6350,"meta":110,"style":110},"\n- (void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray *)views\n\n",[460,12212,12213,12217],{"__ignoreMap":110},[657,12214,12215],{"class":659,"line":660},[657,12216,663],{"emptyLinePlaceholder":127},[657,12218,12219],{"class":659,"line":111},[657,12220,12221],{},"- (void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray *)views\n",[19,12223,12224,12225,12228],{},"You still need to add the code, which handles the annotation, but you can feel free to steal it\nfrom ",[152,12226,8949],{"href":11974,"rel":12227},[156],". The result should look something\nlike this:",[19,12230,12231],{},[33,12232],{"alt":11983,"src":12233},"https://media.synyx.de/uploads//2010/05/screen_synyx_map.png",[19,12235,12236,12237,12240],{},"This concludes our little tutorial on Google Maps and ",[693,12238,12239],{},"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.",[1341,12242,2074],{},{"title":110,"searchDepth":111,"depth":111,"links":12244},[],[120,483],"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":11956,"description":12252},"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",[11416,5047],"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":12258,"title":12259,"author":12260,"body":12261,"category":12334,"date":12335,"description":12336,"extension":124,"link":12337,"meta":12338,"navigation":127,"path":12339,"seo":12340,"slug":12265,"stem":12342,"tags":12343,"teaser":12344,"__hash__":12345},"blog/blog/lessons-learned-iphone-review.md","Lessons learned – iPhone Review",[5023],{"type":12,"value":12262,"toc":12332},[12263,12266,12285,12329],[15,12264,12259],{"id":12265},"lessons-learned-iphone-review",[19,12267,12268,12269,592,12274,592,12279,12284],{},"When you submit an App to the Apple App Store it has to go through a “rigorous quality check”, conducted by Apple.\nAlthough ",[152,12270,12273],{"href":12271,"rel":12272},"http://apprejections.com/index.php/post/171",[156],"there are",[152,12275,12278],{"href":12276,"rel":12277},"http://www.mobileorchard.com/avoiding-iphone-app-rejection-from-apple/",[156],"plenty of resources",[152,12280,12283],{"href":12281,"rel":12282},"https://web.archive.org/web/20100809102557/http://iphone.derheckser.com:80/2009/07/10/suffering-from-modus-operandi-of-reviewer-team/",[156],"out there",",\nhere’s a short rundown of what we’ve learned ourselves so far:",[85,12286,12287,12293,12299,12305,12317],{},[88,12288,12289,12292],{},[164,12290,12291],{},"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.",[88,12294,12295,12298],{},[164,12296,12297],{},"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.",[88,12300,12301,12304],{},[164,12302,12303],{},"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.",[88,12306,12307,12310,12311,12316],{},[164,12308,12309],{},"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 ",[152,12312,12315],{"href":12313,"rel":12314},"http://dlinsin.blogspot.com/2010/05/don-forget-your-linker-flags.html",[156],"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.",[88,12318,12319,12322,12323,12328],{},[164,12320,12321],{},"Don’t infringe Trademarks or Copyrights","\nDon’t mention\nApple, ",[152,12324,12327],{"href":12325,"rel":12326},"http://www.geek.com/articles/mobile/apple-demands-a-developer-removes-android-references-from-iphone-app-2010024/",[156],"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.",[19,12330,12331],{},"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":110,"searchDepth":111,"depth":111,"links":12333},[],[120,483],"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":12259,"description":12341},"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",[5046,5047],"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":12347,"title":12348,"author":12349,"body":12350,"category":12470,"date":12471,"description":12472,"extension":124,"link":12473,"meta":12474,"navigation":127,"path":12475,"seo":12476,"slug":12354,"stem":12478,"tags":12479,"teaser":12481,"__hash__":12482},"blog/blog/user-statistics-from-synyxsudoku.md","User statistics from SynyxSudoku",[617],{"type":12,"value":12351,"toc":12468},[12352,12355,12363,12371,12374,12418,12421,12465],[15,12353,12348],{"id":12354},"user-statistics-from-synyxsudoku",[19,12356,12357,12358,12362],{},"First of all, I was quite surprised as i saw that 70% of the ",[152,12359,11580],{"href":12360,"rel":12361},"http://tinyurl.com/SynyxSudoku",[156]," users that\nuploaded their highscores have also agreed to send us their device specific data, because I really didn’t expect more\nthan 10-20%.",[19,12364,12365,12366,12370],{},"So as you can imagine, we’ve got quite a few samples of data since\nthe ",[152,12367,1362],{"href":12368,"rel":12369},"http://mobile.synyx.de/2010/04/22/release-of-synyxsudoku/",[156],", on that we now want to give you a little\noverview.",[19,12372,12373],{},"The devices came mostly with the latest Android versions available:",[12375,12376,12377,12390],"table",{},[12378,12379,12380],"thead",{},[12381,12382,12383,12387],"tr",{},[12384,12385,12386],"th",{},"Version",[12384,12388,12389],{},"Count",[12391,12392,12393,12402,12410],"tbody",{},[12381,12394,12395,12399],{},[12396,12397,12398],"td",{},"1.6",[12396,12400,12401],{},"39%",[12381,12403,12404,12407],{},[12396,12405,12406],{},"2.1",[12396,12408,12409],{},"11%",[12381,12411,12412,12415],{},[12396,12413,12414],{},"2.1-update1",[12396,12416,12417],{},"50%",[19,12419,12420],{},"What I can say about the reported resolutions is, that the smaller devices (240×320) aren’t that popular as it seems (\nmaybe because there’s only the HTC Tattoo that uses this resolution), but the others are quite evenly matched.",[12375,12422,12423,12432],{},[12378,12424,12425],{},[12381,12426,12427,12430],{},[12384,12428,12429],{},"Resolution",[12384,12431,12389],{},[12391,12433,12434,12442,12450,12458],{},[12381,12435,12436,12439],{},[12396,12437,12438],{},"240×320",[12396,12440,12441],{},"9%",[12381,12443,12444,12447],{},[12396,12445,12446],{},"320×480",[12396,12448,12449],{},"33%",[12381,12451,12452,12455],{},[12396,12453,12454],{},"480×800",[12396,12456,12457],{},"25%",[12381,12459,12460,12463],{},[12396,12461,12462],{},"480×854",[12396,12464,12449],{},[19,12466,12467],{},"In terms of the reported devices, the Motorola Droid and the HTC Desire are at the top of the list, followed by the HTC\nMagic, HTC Tattoo, HTC Legend, G1 and the Sony Ericsson Xperia X10i.",{"title":110,"searchDepth":111,"depth":111,"links":12469},[],[120,263],"2010-05-10T17:30:45","First of all, I was quite surprised as i saw that 70% of the SynyxSudoku users that\\nuploaded their highscores have also agreed to send us their device specific data, because I really didn’t expect more\\nthan 10-20%.","https://synyx.de/blog/user-statistics-from-synyxsudoku/",{},"/blog/user-statistics-from-synyxsudoku",{"title":12348,"description":12477},"First of all, I was quite surprised as i saw that 70% of the SynyxSudoku users that\nuploaded their highscores have also agreed to send us their device specific data, because I really didn’t expect more\nthan 10-20%.","blog/user-statistics-from-synyxsudoku",[133,12480],"statistics","First of all, I was quite surprised as i saw that 70% of the SynyxSudoku users that uploaded their highscores have also agreed to send us their device specific data,…","Lu0rWnhyc5_KUTrPjR1VMv11nvaY7-qq8Fh410R9lDA",{"id":12484,"title":12485,"author":12486,"body":12487,"category":12525,"date":12526,"description":12527,"extension":124,"link":12528,"meta":12529,"navigation":127,"path":12530,"seo":12531,"slug":12533,"stem":12534,"tags":12535,"teaser":12537,"__hash__":12538},"blog/blog/synyxsudoku-update-to-version-1-02.md","SynyxSudoku update to version 1.02",[617],{"type":12,"value":12488,"toc":12523},[12489,12492,12499,12502,12513,12516,12519],[15,12490,12485],{"id":12491},"synyxsudoku-update-to-version-102",[19,12493,12494,12495,12498],{},"There were a few little things that had to be changed on SynyxSudoku after\nthe ",[152,12496,1362],{"href":12368,"rel":12497},[156],", so today we uploaded an update to the version\n1.02.",[19,12500,12501],{},"Here’s a quick changelog:",[85,12503,12504,12507,12510],{},[88,12505,12506],{},"You can now only resume a game after a restart of the app, if you started a game in the previous session (or before)\nand didn’t solve it. (If the app was moved into the background while a solved game was active, you can still resume it\nif you like. It’s only gone if you close the app.)",[88,12508,12509],{},"If you created a game and restarted the app twice you without resuming the sudoku on the first time, you couldn’t\nresume it the second time, it only displayed a empty field (Thanks to Markus who found this bug).",[88,12511,12512],{},"The sleep mode is now deactivated if you are on the sudoku screen. (It’s quite annoying if you are in midst of your\nthinking process and then the screen suddenly turns black…)",[19,12514,12515],{},"If you notice any bugs or if something bothers you about the game, please leave a comment, or write us an email.",[19,12517,12518],{},"Download:",[19,12520,12521],{},[33,12522],{"alt":110,"src":12360},{"title":110,"searchDepth":111,"depth":111,"links":12524},[],[120,263],"2010-05-10T14:09:19","There were a few little things that had to be changed on SynyxSudoku after\\nthe release, so today we uploaded an update to the version\\n1.02.","https://synyx.de/blog/synyxsudoku-update-to-version-1-02/",{},"/blog/synyxsudoku-update-to-version-1-02",{"title":12485,"description":12532},"There were a few little things that had to be changed on SynyxSudoku after\nthe release, so today we uploaded an update to the version\n1.02.","synyxsudoku-update-to-version-1-02","blog/synyxsudoku-update-to-version-1-02",[133,12536],"sudoku","There were a few little things that had to be changed on SynyxSudoku after the release, so today we uploaded an update to the version 1.02. Here’s a quick changelog:…","mUUFtoMEioYpW-319oulDYaHulQXuJwqKQ9BWwLeFqc",{"id":12540,"title":12541,"author":12542,"body":12544,"category":12702,"date":12703,"description":12704,"extension":124,"link":12705,"meta":12706,"navigation":127,"path":12707,"seo":12708,"slug":12548,"stem":12710,"tags":12711,"teaser":12713,"__hash__":12714},"blog/blog/google-maps-on-android-part-2-overlays.md","Google Maps on Android – Part 2: Overlays",[12543],"heib",{"type":12,"value":12545,"toc":12700},[12546,12549,12558,12575,12578,12581,12584,12587,12590,12593,12596,12607,12621,12624,12627,12630,12632,12639,12642,12645,12661,12664,12667,12670,12672,12678],[15,12547,12541],{"id":12548},"google-maps-on-android-part-2-overlays",[19,12550,12551,12552,12557],{},"In my ",[152,12553,12556],{"href":12554,"rel":12555},"http://mobile.synyx.de/2010/04/30/google-maps-on-android/",[156],"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.",[19,12559,12560,12561,12566,12567,12570,12571,12574],{},"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 ",[152,12562,12565],{"href":12563,"rel":12564},"http://developer.android.com/resources/tutorials/views/hello-mapview.html",[156],"tutorial on the android developer site",",\nalready mentioned in the ",[152,12568,11489],{"href":12554,"rel":12569},[156],". The\n",[693,12572,12573],{},"HelloItemizedOverlay"," that is created there, enables you to add as many markers to your map as you want:",[19,12576,12577],{},"`List mapOverlays = mapView.getOverlays();",[19,12579,12580],{},"Drawable drawable = this.getResources().getDrawable(R.drawable.synyxLogo);",[19,12582,12583],{},"HelloItemizedOverlay itemizedOverlay = new HelloItemizedOverlay(drawable);",[19,12585,12586],{},"OverlayItem synyxOfficeOverlay = new OverlayItem(synyxOfficeLocation, \"Synyx\", \"This is the Synyx office!\");",[19,12588,12589],{},"itemizedOverlay.addOverlay(synyxOfficeOverlay);",[19,12591,12592],{},"// add more overlayItems...",[19,12594,12595],{},"mapOverlays.add(itemizedOverlay);`",[19,12597,12598,12599,12602,12603,12606],{},"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 ",[693,12600,12601],{},"setMarker()"," method of the ",[693,12604,12605],{},"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.",[19,12608,12609,12610,12613,12614,12617,12618,12620],{},"So how to do it? The trick is the static method ",[693,12611,12612],{},"boundCenterBottom()"," of the ",[693,12615,12616],{},"ItemizedOverlay"," class. This method is\nalso called in the constructor of the ",[693,12619,12573],{},", when the default marker is set:",[19,12622,12623],{},"`public RouteMapOverlay(Drawable defaultMarker, Context context) {",[19,12625,12626],{},"super(boundCenterBottom(defaultMarker));",[19,12628,12629],{},"this.context = context;",[19,12631,11320],{},[19,12633,12634,12635,12638],{},"The problem is, that the visibility of this method is set to ",[693,12636,12637],{},"protected"," (why?!). So calling it when setting the marker\nwon’t work:",[19,12640,12641],{},"`// won't work as boundCenterBottom is protected",[19,12643,12644],{},"synyxOfficeOverlay.setOverlay(ItemizedOverlay.boundCenterBottom(myNewMarker));`",[19,12646,12647,12648,12650,12651,12653,12654,12657,12658,12660],{},"Because of this visibility feature, the “easier” way is to add a new setter to your ",[693,12649,12573],{}," class,\naccepting an ",[693,12652,12605],{}," and a ",[693,12655,12656],{},"Drawable"," to use as the marker. Within that setter, you are able to call the\n",[693,12659,12612],{}," method to set up the marker correctly:",[19,12662,12663],{},"`public void addOverlay(OverlayItem overlayItem, Drawable marker) {",[19,12665,12666],{},"overlayItem.setMarker(boundCenterBottom(marker));",[19,12668,12669],{},"addOverlay(overlayItem);",[19,12671,11320],{},[19,12673,12674,12675,12677],{},"Now if you use that setter, the map will correctly show your ",[693,12676,12605],{}," with your marker.",[19,12679,12680,12681,12683,12684,12686,12687,12690,12691,12694,12695,12697,12698,5074],{},"Btw: instead of calling ",[693,12682,12612],{}," (which places the bottom centre of your ",[693,12685,12656],{}," over the defined\n",[693,12688,12689],{},"GeoPoint","), you can also use ",[693,12692,12693],{},"boundCenter()"," to place the centre of your ",[693,12696,12656],{}," over the ",[693,12699,12689],{},{"title":110,"searchDepth":111,"depth":111,"links":12701},[],[120,483],"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":12541,"description":12709},"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",[133,11416,12712,10767],"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":12716,"title":12717,"author":12718,"body":12719,"category":12920,"date":12921,"description":12922,"extension":124,"link":12923,"meta":12924,"navigation":127,"path":12925,"seo":12926,"slug":12723,"stem":12928,"tags":12929,"teaser":12930,"__hash__":12931},"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",[5023],{"type":12,"value":12720,"toc":12918},[12721,12725,12737,12744,12753,12756,12764,12780,12792,12845,12860,12894,12913,12916],[15,12722,12724],{"id":12723},"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",[19,12726,12727,12728,12730,12731,12736],{},"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 ",[693,12729,12239],{}," framework. I won’t go into the details of MapKit here, since Apple’s documentation is\nawesome and they provide a lot\nof ",[152,12732,12735],{"href":12733,"rel":12734},"http://developer.apple.com/iphone/library/samplecode/CurrentAddress/Introduction/Intro.html#//apple_ref/doc/uid/DTS40009469",[156],"sample code",",\nwhich gets you up and running in no time.",[19,12738,12739,12740,12743],{},"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 ",[693,12741,12742],{},"MapView"," is loaded. Here are a couple of screenshots to give you an idea of what I’m talking about:",[19,12745,12746,592,12748,12752],{},[33,12747],{"alt":11983,"src":12233},[33,12749],{"alt":12750,"src":12751},"\"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.",[19,12754,12755],{},"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:",[519,12757,12758,12761],{},[88,12759,12760],{},"Showing the Synyx Offices on the Map",[88,12762,12763],{},"Zooming in, so that everything fits on the screen",[19,12765,12766,12767,12769,12770,12773,12774,12779],{},"If you feel like going off on your own, the code for the ",[693,12768,12002],{}," used, is available for download\non ",[152,12771,8949],{"href":11974,"rel":12772},[156],". The zooming algorithm is borrowed\nfrom ",[152,12775,12778],{"href":12776,"rel":12777},"http://stackoverflow.com/questions/1303265/algorithm-for-determining-minimum-bounding-rectangle-for-collection-of-lat-lon-co/1413264#1413264",[156],"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!",[19,12781,12782,12783,12785,12786,12788,12789,12791],{},"So let’s get into it. We first need a ",[693,12784,12002],{}," with a ",[693,12787,12742],{}," 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 ",[693,12790,12239],{}," annotation:",[649,12793,12795],{"className":6348,"code":12794,"language":6350,"meta":110,"style":110},"\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",[460,12796,12797,12801,12806,12811,12816,12821,12825,12830,12835,12840],{"__ignoreMap":110},[657,12798,12799],{"class":659,"line":660},[657,12800,663],{"emptyLinePlaceholder":127},[657,12802,12803],{"class":659,"line":111},[657,12804,12805],{},"@interface AddressAnnotation : NSObject\u003CMKAnnotation> {\n",[657,12807,12808],{"class":659,"line":671},[657,12809,12810],{}," CLLocationCoordinate2D coordinate;\n",[657,12812,12813],{"class":659,"line":677},[657,12814,12815],{}," NSString *title;\n",[657,12817,12818],{"class":659,"line":683},[657,12819,12820],{}," NSString *subtitle;\n",[657,12822,12823],{"class":659,"line":732},[657,12824,2438],{},[657,12826,12827],{"class":659,"line":738},[657,12828,12829],{},"- (id)initWith:(CLLocationCoordinate2D)_coords;\n",[657,12831,12832],{"class":659,"line":744},[657,12833,12834],{},"@property(retain,nonatomic) NSString *title;\n",[657,12836,12837],{"class":659,"line":750},[657,12838,12839],{},"@property(retain,nonatomic) NSString *subtitle;\n",[657,12841,12842],{"class":659,"line":756},[657,12843,12844],{},"@end\n",[19,12846,12847,12848,12852,12853,12856,12857,5074],{},"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 ",[152,12849,12851],{"href":11974,"rel":12850},[156],"synyxLocation (line 175)",". It simply\nreturns the ",[693,12854,12855],{},"CLLocationCoordinate2D"," struct, which is needed in our ",[693,12858,12859],{},"AddressAnnotation",[649,12861,12863],{"className":6348,"code":12862,"language":6350,"meta":110,"style":110},"\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",[460,12864,12865,12869,12874,12879,12884,12889],{"__ignoreMap":110},[657,12866,12867],{"class":659,"line":660},[657,12868,663],{"emptyLinePlaceholder":127},[657,12870,12871],{"class":659,"line":111},[657,12872,12873],{},"CLLocationCoordinate2D location = [self synyxLocation];\n",[657,12875,12876],{"class":659,"line":671},[657,12877,12878],{},"self.synyx = [[AddressAnnotation alloc] initWith:location];\n",[657,12880,12881],{"class":659,"line":677},[657,12882,12883],{},"[self.synyx setTitle:@\"Synyx GmbH & Co. KG\"];\n",[657,12885,12886],{"class":659,"line":683},[657,12887,12888],{},"[self.synyx setSubtitle:synyxLoc];\n",[657,12890,12891],{"class":659,"line":732},[657,12892,12893],{},"[mapView addAnnotation:synyx];\n",[19,12895,12896,12897,12899,12900,12902,12903,12613,12906,12908,12909,12912],{},"Instantiating the ",[693,12898,12859],{}," with the previously determined location and adding it to the ",[693,12901,12742],{}," will to the\nrest. I did this in the ",[693,12904,12905],{},"viewDidLoad",[693,12907,12002],{},", which is not be the best place. Maybe the\n",[693,12910,12911],{},"viewWillAppear"," method would have been better.",[19,12914,12915],{},"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.",[1341,12917,2074],{},{"title":110,"searchDepth":111,"depth":111,"links":12919},[],[120,483],"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":12717,"description":12927},"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",[11416,5047],"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":12933,"title":12934,"author":12935,"body":12936,"category":13007,"date":13008,"description":13009,"extension":124,"link":13010,"meta":13011,"navigation":127,"path":13012,"seo":13013,"slug":13015,"stem":13016,"tags":13017,"teaser":13019,"__hash__":13020},"blog/blog/google-maps-on-android.md","Google Maps on Android – Part 1: Navigation",[12543],{"type":12,"value":12937,"toc":13005},[12938,12941,12947,12959,12964,12976,12979,12982,12985,12988],[15,12939,12934],{"id":12940},"google-maps-on-android-part-1-navigation",[19,12942,12943,12944,5074],{},"Integrating a google map on android is quiet simple – how to do this basically is shown in\nthe ",[152,12945,12565],{"href":12563,"rel":12946},[156],[19,12948,12949,12950,12955,12956,12958],{},"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 ",[152,12951,12954],{"href":12952,"rel":12953},"http://maps.google.com",[156],"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 ",[693,12957,12689],{}," in your Android app:",[19,12960,12961],{},[460,12962,12963],{},"GeoPoint synyxOfficeLocation = new GeoPoint(49002175, 8394160);",[19,12965,12966,12967,12969,12970,12972,12973,6330],{},"As the ",[693,12968,12689],{}," 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 ",[693,12971,12742],{}," has a ",[693,12974,12975],{},"MapController",[19,12977,12978],{},"`MapController mapController = mapView.getController();",[19,12980,12981],{},"mapController.setCenter(synyxOfficeLocation);",[19,12983,12984],{},"mapController.setZoom(20);`",[19,12986,12987],{},"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.",[19,12989,12990,12991,4845,12994,12189,12997,13000,13001,13004],{},"The above mentioned methods change the map in a very static way. For some more visual effects try ",[693,12992,12993],{},"animateTo()",[693,12995,12996],{},"zoomIn()",[693,12998,12999],{},"zoomOut()"," to change the view of the map by showing a short animation. An also very helpful method is\n",[693,13002,13003],{},"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":110,"searchDepth":111,"depth":111,"links":13006},[],[120,483],"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":12934,"description":13014},"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",[133,11416,13018],"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":13022,"title":13023,"author":13024,"body":13025,"category":13068,"date":13069,"description":13070,"extension":124,"link":13071,"meta":13072,"navigation":127,"path":13073,"seo":13074,"slug":13029,"stem":13075,"tags":13076,"teaser":13078,"__hash__":13079},"blog/blog/release-of-synyxsudoku.md","Release of SynyxSudoku",[617],{"type":12,"value":13026,"toc":13066},[13027,13030,13033,13036,13039,13042,13045,13048,13052,13055],[15,13028,13023],{"id":13029},"release-of-synyxsudoku",[19,13031,13032],{},"We are proud to announce, that our sudoku app, “SynyxSudoku” is now available on the android store. And best of all:\nIt’s completely free of charge !(except for your traffic costs, of course 🙂 )",[19,13034,13035],{},"SynyxSudoku offers 3 difficulty levels, containing nearly unlimited puzzling fun due to new created sudokus each time\nyou launch!",[19,13037,13038],{},"And even if none of these difficulty levels fits your needs – SynyxSudoku has the option to let you create a difficulty\nlevel of your own.",[19,13040,13041],{},"You can also save your best performances in solving the sudokus in the highscore and even upload it to our server to\ncompete with other sudoku players around the world to finally answer the question: “Who is the fastest sudoku-solver?”.",[19,13043,13044],{},"Need a little break during a game? No problem! Just pause, or even close it and you can continue the next time you\nstart. And if somebody calls you in midst of a game, it is automatically paused!",[19,13046,13047],{},"Download our game by searching for Synyx Sudoku on the android market or using this qr-code:",[19,13049,13050],{},[33,13051],{"alt":110,"src":12360},[19,13053,13054],{},"If you are curious how SynyxSudoku looks like, here we have a few screenshots:",[19,13056,13057,592,13060,592,13063],{},[33,13058],{"alt":110,"src":13059},"https://media.synyx.de/uploads//2010/04/sudoku_1.png",[33,13061],{"alt":110,"src":13062},"https://media.synyx.de/uploads//2010/04/sudoku_menu_en.png",[33,13064],{"alt":110,"src":13065},"https://media.synyx.de/uploads//2010/04/sudoku_4_en.png",{"title":110,"searchDepth":111,"depth":111,"links":13067},[],[120,263],"2010-04-22T17:12:34","We are proud to announce, that our sudoku app, “SynyxSudoku” is now available on the android store. And best of all:\\nIt’s completely free of charge !(except for your traffic costs, of course 🙂 )","https://synyx.de/blog/release-of-synyxsudoku/",{},"/blog/release-of-synyxsudoku",{"title":13023,"description":13032},"blog/release-of-synyxsudoku",[133,13077,12536],"gaming","We are proud to announce, that our sudoku app, “SynyxSudoku” is now available on the android store. And best of all: It’s completely free of charge ! (except for your traffic costs,…","MEK2L19-Qxl007OJY5Iv85w14HBR-yyjszz0hZsjCbI",{"id":13081,"title":13082,"author":13083,"body":13084,"category":13135,"date":13136,"description":13137,"extension":124,"link":13138,"meta":13139,"navigation":127,"path":13140,"seo":13141,"slug":13142,"stem":13143,"tags":13144,"teaser":13145,"__hash__":13146},"blog/blog/howto-startup-with-maemo-and-qt-4-6.md","Howto startup with Maemo and Qt 4.6 – Step1: Setup",[8494],{"type":12,"value":13085,"toc":13133},[13086,13089,13092,13095,13098,13101,13104,13111,13118,13125],[15,13087,13082],{"id":13088},"howto-startup-with-maemo-and-qt-46-step1-setup",[19,13090,13091],{},"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.",[19,13093,13094],{},"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:",[19,13096,13097],{},"`#scratbox maemo sdk fix",[19,13099,13100],{},"vm.mmap_min_addr = 0`",[19,13102,13103],{},"I installed the following tools in the given order:",[19,13105,13106,13107],{},"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: ",[152,13108,13109],{"href":13109,"rel":13110},"http://wiki.maemo.org/Documentation/Maemo_5_Final_SDK_Installation",[156],[19,13112,13113,13114],{},"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: ",[152,13115,13116],{"href":13116,"rel":13117},"http://esbox.garage.maemo.org/2nd_edition/installation_product.html",[156],[19,13119,13120,13121],{},"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 : ",[152,13122,13123],{"href":13123,"rel":13124},"http://qt.nokia.com/developer/eclipse-integration",[156],[19,13126,6037,13127,13132],{},[152,13128,13131],{"href":13129,"rel":13130},"http://mobile.synyx.de/2010/06/howto-startup-with-maemo-and-qt-4-6-%E2%80%93-step2-configure-ide/",[156],"next post"," i will\ndescribe howto prepare your ide for Qt developing.",{"title":110,"searchDepth":111,"depth":111,"links":13134},[],[120,483],"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":13082,"description":13091},"howto-startup-with-maemo-and-qt-4-6","blog/howto-startup-with-maemo-and-qt-4-6",[8609,11417],"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",{"id":13148,"title":13149,"author":13150,"body":13151,"category":13169,"date":13170,"description":13158,"extension":124,"link":13171,"meta":13172,"navigation":127,"path":13173,"seo":13174,"slug":13175,"stem":13176,"tags":13177,"teaser":13178,"__hash__":13179},"blog/blog/sudoku-on-android-2.md","Sudoku on Android",[617],{"type":12,"value":13152,"toc":13167},[13153,13156,13159,13164],[15,13154,13149],{"id":13155},"sudoku-on-android",[19,13157,13158],{},"Here’s a quick preview of our upcoming sudoku app on android:",[19,13160,13161],{},[657,13162,13163],{},"youtube S_wjWf7V660",[19,13165,13166],{},"The layout has yet to be polished a little, but altogether the app is nearly finished.",{"title":110,"searchDepth":111,"depth":111,"links":13168},[],[120,263],"2010-04-15T18:49:51","https://synyx.de/blog/sudoku-on-android-2/",{},"/blog/sudoku-on-android-2",{"title":13149,"description":13158},"sudoku-on-android-2","blog/sudoku-on-android-2",[133,13077],"Here’s a quick preview of our upcoming sudoku app on android: [youtube S_wjWf7V660] The layout has yet to be polished a little, but altogether the app is nearly finished.","tzc504LoreZVNA5CrnE9g9ba4AGugW0r1Z80n8PaOmU",{"id":13181,"title":13182,"author":13183,"body":13184,"category":13219,"date":13220,"description":13221,"extension":124,"link":13222,"meta":13223,"navigation":127,"path":13224,"seo":13225,"slug":13188,"stem":13227,"tags":13228,"teaser":13230,"__hash__":13231},"blog/blog/welcome.md","Welcome",[5023],{"type":12,"value":13185,"toc":13217},[13186,13189,13209],[15,13187,13182],{"id":13188},"welcome",[19,13190,13191,13192,4799,13197,13202,13203,13208],{},"This is the inception of Synyx Mobile Solutions. In the near future we’ll blog here about a wide range of topics in the\nmobile space. No matter if it’s ",[152,13193,13196],{"href":13194,"rel":13195},"http://android.org",[156],"Google’s Android",[152,13198,13201],{"href":13199,"rel":13200},"http://apple.com/iphone",[156],"Apple’s iPhone","\nor ",[152,13204,13207],{"href":13205,"rel":13206},"http://www.meego.com/",[156],"MeeGo"," we have something to say about it!",[19,13210,13211,13212,5074],{},"Stay tuned and subscribe to our ",[152,13213,13216],{"href":13214,"rel":13215},"http://mobile.synyx.de/?feed=rss2",[156],"RSS feed",{"title":110,"searchDepth":111,"depth":111,"links":13218},[],[120],"2010-03-26T17:24:38","This is the inception of Synyx Mobile Solutions. In the near future we’ll blog here about a wide range of topics in the\\nmobile space. No matter if it’s Google’s Android, Apple’s iPhone\\nor MeeGo we have something to say about it!","https://synyx.de/blog/welcome/",{},"/blog/welcome",{"title":13182,"description":13226},"This is the inception of Synyx Mobile Solutions. In the near future we’ll blog here about a wide range of topics in the\nmobile space. No matter if it’s Google’s Android, Apple’s iPhone\nor MeeGo we have something to say about it!","blog/welcome",[133,5047,13229,607],"meego","This is the inception of Synyx Mobile Solutions. In the near future we’ll blog here about a wide range of topics in the mobile space. No matter if it’s Google’s…","fxYe2l28A3Tr4AN4j9XfZ6v3dxy5234kvArmmK3JoB4",[13233,13236,13239,13242,13244,13247,13250,13253,13256,13259,13262,13265,13268,13271,13274,13277,13280,13283,13286,13289,13292,13295,13297,13300,13303,13306,13309,13311,13314,13317,13320,13323,13326,13329,13332,13335,13337,13340,13343,13346,13349,13352,13354,13357,13359,13362,13365,13368,13371,13374,13376,13379,13382,13385,13388,13391,13394,13397,13400,13403,13406,13409,13412,13414,13417,13420,13423,13426,13428,13431,13434,13437,13440,13443,13445,13448,13451,13454,13457,13460,13463,13465,13468,13471,13474,13477,13480,13483,13486,13489,13492,13495,13498,13501,13504,13507,13510,13513,13516,13518,13521,13524,13527,13530,13532,13535,13538,13541,13544,13547,13550,13553,13556,13559,13562,13565,13568,13571,13574,13577,13580,13583,13586,13589,13592,13595,13598,13601,13604,13607,13608,13611,13614,13617,13620,13623,13626,13628,13631,13634,13637,13640],{"slug":13234,"name":13235},"abel","Jennifer Abel",{"slug":13237,"name":13238},"allmendinger","Otto Allmendinger",{"slug":13240,"name":13241},"antony","Ben Antony",{"slug":11821,"name":13243},"Joachim Arrasz",{"slug":13245,"name":13246},"bauer","David Bauer",{"slug":13248,"name":13249},"bechtold","Janine Bechtold",{"slug":13251,"name":13252},"boersig","Jasmin Börsig",{"slug":13254,"name":13255},"buch","Fabian Buch",{"slug":13257,"name":13258},"buchloh","Aljona Buchloh",{"slug":13260,"name":13261},"burgard","Julia Burgard",{"slug":13263,"name":13264},"caspar-schwedes","Caspar Schwedes",{"slug":13266,"name":13267},"christina-schmitt","Christina Schmitt",{"slug":13269,"name":13270},"clausen","Michael Clausen",{"slug":13272,"name":13273},"contargo_poetzsch","Thomas Pötzsch",{"slug":13275,"name":13276},"damrath","Sebastian Damrath",{"slug":13278,"name":13279},"daniel","Markus Daniel",{"slug":13281,"name":13282},"dasch","Julia Dasch",{"slug":13284,"name":13285},"denman","Joffrey Denman",{"slug":13287,"name":13288},"dfuchs","Daniel Fuchs",{"slug":13290,"name":13291},"dobler","Max Dobler",{"slug":13293,"name":13294},"dobriakov","Vladimir Dobriakov",{"slug":13296,"name":13296},"dreiqbik",{"slug":13298,"name":13299},"dschaefer","Denise Schäfer",{"slug":13301,"name":13302},"dschneider","Dominik Schneider",{"slug":13304,"name":13305},"duerlich","Isabell Duerlich",{"slug":13307,"name":13308},"dutkowski","Bernd Dutkowski",{"slug":13310,"name":13310},"eifler",{"slug":13312,"name":13313},"essig","Tim Essig",{"slug":13315,"name":13316},"ferstl","Maximilian Ferstl",{"slug":13318,"name":13319},"fey","Prisca Fey",{"slug":13321,"name":13322},"frank","Leonard Frank",{"slug":13324,"name":13325},"franke","Arnold Franke",{"slug":13327,"name":13328},"frischer","Nicolette Rudmann",{"slug":13330,"name":13331},"fuchs","Petra Fuchs",{"slug":13333,"name":13334},"gari","Sarah Gari",{"slug":8494,"name":13336},"Gast",{"slug":13338,"name":13339},"graf","Johannes Graf",{"slug":13341,"name":13342},"grammlich","Daniela Grammlich",{"slug":13344,"name":13345},"guthardt","Sabrina Guthardt",{"slug":13347,"name":13348},"haeussler","Johannes Häussler",{"slug":13350,"name":13351},"hammann","Daniel Hammann",{"slug":10,"name":13353},"Julian Heetel",{"slug":13355,"name":13356},"heft","Florian Heft",{"slug":12543,"name":13358},"Sebastian Heib",{"slug":13360,"name":13361},"heisler","Ida Heisler",{"slug":13363,"name":13364},"helm","Patrick Helm",{"slug":13366,"name":13367},"herbold","Michael Herbold",{"slug":13369,"name":13370},"hofmann","Peter Hofmann",{"slug":13372,"name":13373},"hopf","Florian Hopf",{"slug":9,"name":13375},"Alina Jaud",{"slug":13377,"name":13378},"jayasinghe","Robin De Silva Jayasinghe",{"slug":13380,"name":13381},"jbuch","Jonathan Buch",{"slug":13383,"name":13384},"junghanss","Gitta Junghanß",{"slug":13386,"name":13387},"kadyietska","Khrystyna Kadyietska",{"slug":13389,"name":13390},"kannegiesser","Marc Kannegiesser",{"slug":13392,"name":13393},"karoly","Robert Károly",{"slug":13395,"name":13396},"karrasz","Katja Arrasz-Schepanski",{"slug":13398,"name":13399},"kaufmann","Florian Kaufmann",{"slug":13401,"name":13402},"kesler","Mike Kesler",{"slug":13404,"name":13405},"kirchgaessner","Bettina Kirchgäßner",{"slug":13407,"name":13408},"klem","Yannic Klem",{"slug":13410,"name":13411},"klenk","Timo Klenk",{"slug":617,"name":13413},"Tobias Knell",{"slug":13415,"name":13416},"knoll","Anna-Lena Knoll",{"slug":13418,"name":13419},"knorre","Matthias Knorre",{"slug":13421,"name":13422},"koenig","Melanie König",{"slug":13424,"name":13425},"kraft","Thomas Kraft",{"slug":4962,"name":13427},"Florian Krupicka",{"slug":13429,"name":13430},"kuehn","Christian Kühn",{"slug":13432,"name":13433},"lange","Christian Lange",{"slug":13435,"name":13436},"larrasz","Luca Arrasz",{"slug":13438,"name":13439},"leist","Sascha Leist",{"slug":13441,"name":13442},"lihs","Michael Lihs",{"slug":5023,"name":13444},"David Linsin",{"slug":13446,"name":13447},"maniyar","Christian Maniyar",{"slug":13449,"name":13450},"martin","Björnie",{"slug":13452,"name":13453},"martin-koch","Martin Koch",{"slug":13455,"name":13456},"matt","Tobias Matt",{"slug":13458,"name":13459},"mennerich","Christian Mennerich",{"slug":13461,"name":13462},"menz","Alexander Menz",{"slug":498,"name":13464},"Frederick Meseck",{"slug":13466,"name":13467},"messner","Oliver Messner",{"slug":13469,"name":13470},"michael-ploed","Michael Plöd",{"slug":13472,"name":13473},"mies","Marius Mies",{"slug":13475,"name":13476},"mihai","Alina Mihai",{"slug":13478,"name":13479},"moeller","Jörg Möller",{"slug":13481,"name":13482},"mohr","Rebecca Mohr",{"slug":13484,"name":13485},"moretti","David Moretti",{"slug":13487,"name":13488},"mueller","Sven Müller",{"slug":13490,"name":13491},"muessig","Alexander Müssig",{"slug":13493,"name":13494},"neupokoev","Grigory Neupokoev",{"slug":13496,"name":13497},"nussbaecher","Carmen Nussbächer",{"slug":13499,"name":13500},"ochs","Pascal Ochs",{"slug":13502,"name":13503},"oelhoff","Jan Oelhoff",{"slug":13505,"name":13506},"oengel","Yasin Öngel",{"slug":13508,"name":13509},"oezsoy","Enis Özsoy",{"slug":13511,"name":13512},"posch","Maya Posch",{"slug":13514,"name":13515},"ralfmueller","Ralf Müller",{"slug":13517,"name":13517},"redakteur",{"slug":13519,"name":13520},"reich","Michael Reich",{"slug":13522,"name":13523},"reinhard","Karl-Ludwig Reinhard",{"slug":13525,"name":13526},"rmueller","Rebecca Müller",{"slug":13528,"name":13529},"rosum","Jan Rosum",{"slug":13531,"name":13531},"rueckert",{"slug":13533,"name":13534},"ruessel","Sascha Rüssel",{"slug":13536,"name":13537},"sauter","Moritz Sauter",{"slug":13539,"name":13540},"schaefer","Julian Schäfer",{"slug":13542,"name":13543},"scherer","Petra Scherer",{"slug":13545,"name":13546},"schlicht","Anne Schlicht",{"slug":13548,"name":13549},"schmidt","Jürgen Schmidt",{"slug":13551,"name":13552},"schneider","Tobias Schneider",{"slug":13554,"name":13555},"seber","Benjamin Seber",{"slug":13557,"name":13558},"sommer","Marc Sommer",{"slug":13560,"name":13561},"speaker-fels","Jakob Fels",{"slug":13563,"name":13564},"speaker-gierke","Oliver Gierke",{"slug":13566,"name":13567},"speaker-krupa","Malte Krupa",{"slug":13569,"name":13570},"speaker-mader","Jochen Mader",{"slug":13572,"name":13573},"speaker-meusel","Tim Meusel",{"slug":13575,"name":13576},"speaker-milke","Oliver Milke",{"slug":13578,"name":13579},"speaker-paluch","Mark Paluch",{"slug":13581,"name":13582},"speaker-schad","Jörg Schad",{"slug":13584,"name":13585},"speaker-schalanda","Jochen Schalanda",{"slug":13587,"name":13588},"speaker-schauder","Jens Schauder",{"slug":13590,"name":13591},"speaker-unterstein","Johannes Unterstein",{"slug":13593,"name":13594},"speaker-wolff","Eberhard Wolff",{"slug":13596,"name":13597},"speaker-zoerner","Stefan Zörner",{"slug":13599,"name":13600},"stefan-belger","Stefan Belger",{"slug":13602,"name":13603},"steinegger","Roland Steinegger",{"slug":13605,"name":13606},"stern","sternchen synyx",{"slug":607,"name":607},{"slug":13609,"name":13610},"szulc","Mateusz Szulc",{"slug":13612,"name":13613},"tamara","Tamara Tunczinger",{"slug":13615,"name":13616},"theuer","Tobias Theuer",{"slug":13618,"name":13619},"thieme","Sandra Thieme",{"slug":13621,"name":13622},"thies-clasen","Marudor",{"slug":13624,"name":13625},"toernstroem","Olle Törnström",{"slug":427,"name":13627},"Max Ullinger",{"slug":13629,"name":13630},"ulrich","Stephan Ulrich",{"slug":13632,"name":13633},"wagner","Stefan Wagner",{"slug":13635,"name":13636},"weigel","Andreas Weigel",{"slug":13638,"name":13639},"werner","Fabian Werner",{"slug":13641,"name":13642},"wolke","Sören Wolke",["Reactive",13644],{"$scookieConsent":13645,"$ssite-config":13647},{"functional":13646,"analytics":13646},false,{"_priority":13648,"env":13652,"name":13653,"url":13654},{"name":13649,"env":13650,"url":13651},-10,-15,0,"production","nuxt-app","https://synyx.de",["Set"],["ShallowReactive",13657],{"category-mobile-blog":-1,"authors":-1},"/blog/kategorien/mobile-blog"]