\n \u003Cinclude relativeToChangelogFile=\"true\" file=\"install/all/procedures.xml\"/>\n ...\n \u003Cinclude relativeToChangelogFile=\"true\" file=\"changes/all/table_add_column_xyz.xml\"/>\n \u003Cinclude relativeToChangelogFile=\"true\" file=\"changes/${projectname.branch}/adjust_procedure_asd.xml\"/>\n ...\n\u003C/databaseChangeLog>\n",[73,2922,2923,2928,2933,2938,2943,2948,2953,2958,2963,2968,2972],{"__ignoreMap":11},[76,2924,2925],{"class":78,"line":79},[76,2926,2927],{},"\u003C?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n",[76,2929,2930],{"class":78,"line":12},[76,2931,2932],{},"\u003CdatabaseChangeLog xmlns=\"http://www.liquibase.org/xml/ns/dbchangelog\"\n",[76,2934,2935],{"class":78,"line":90},[76,2936,2937],{}," xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n",[76,2939,2940],{"class":78,"line":96},[76,2941,2942],{}," xsi:schemaLocation=\"http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-2.0.xsd\">\n",[76,2944,2945],{"class":78,"line":102},[76,2946,2947],{}," \u003Cinclude relativeToChangelogFile=\"true\" file=\"install/all/tables.xml\"/>\n",[76,2949,2950],{"class":78,"line":108},[76,2951,2952],{}," \u003Cinclude relativeToChangelogFile=\"true\" file=\"install/all/procedures.xml\"/>\n",[76,2954,2955],{"class":78,"line":114},[76,2956,2957],{}," ...\n",[76,2959,2960],{"class":78,"line":120},[76,2961,2962],{}," \u003Cinclude relativeToChangelogFile=\"true\" file=\"changes/all/table_add_column_xyz.xml\"/>\n",[76,2964,2965],{"class":78,"line":125},[76,2966,2967],{}," \u003Cinclude relativeToChangelogFile=\"true\" file=\"changes/${projectname.branch}/adjust_procedure_asd.xml\"/>\n",[76,2969,2970],{"class":78,"line":130},[76,2971,2957],{},[76,2973,2974],{"class":78,"line":136},[76,2975,2976],{},"\u003C/databaseChangeLog>\n",[41,2978,2979,2980,2985],{},"As you can see, we have used the ",[903,2981,2982],{},[1159,2983,2984],{},"${projectname.branch}"," placeholder in the path of a changelog. The file that is\nreferenced there, has to be added for each of the branches, because this changelog is also used for every branch. This\ncan be somewhat inconvenient in some times, when you only have to add a change to one of the branches, but that should\nnot happen that often. It’s more likely (at least for our case) that you have to adjust the same thing for all branches,\nbut a little differnt, or fill some table with different data.",[41,2987,2988],{},"Also, the right execution order of the scripts is secured this way. Furthermore, we don’t have to create and update one\nchangelog for every branch, where it can easily happen, that one file is left out and it goes through unnoticed. In our\nsetup, if you forget to add a file that’s declared in the changelog, that’s another case, because you will know it as\nsoon as you execute the script for the specific branch. So we considered this to be the best method to address multiple\nbranches.",[41,2990,2991,2992,2995,2996,2999],{},"You can also use the placeholder in other places, like the ",[1159,2993,2994],{},"loadUpdateData"," tag, where you can specify a .csv file from\nwhich liquibase will load data. There, You’ll only need to add the changelog to the ‘",[1159,2997,2998],{},"all","‘ folder and the .csv files in\neach branch folder. Furthermore, we are",[621,3001,3003],{"id":3002},"maven-profiles","maven profiles",[41,3005,3006],{},"To configure and execute liquibase, we use different maven profiles. We need to specify the url, username and password\nfor each server, so we have one profile for each of them. The properties that are the same based on the environment (\ntest, stage, prod), are defined in a config file included from the pom (as already seen above), so we also need to add a\nproperty for the environment in each profile. Like this we can create a liquibase profile for each application of an\nenvironment of a branch (yup, there are quite some profiles because of this, but it is simply needed – you don’t have to\nkeep them in your settings.xml all the time, though, so it isn’t that much of a pain, once they are created 😛 ). By\nsetting the username and password locally in the maven settings.xml, we also keep sure that no passwords are commited in\nour version control.",[41,3008,3009],{},"example profile:",[67,3011,3013],{"className":1124,"code":3012,"language":1126,"meta":11,"style":11},"\n \u003Cprofile>\n \u003Cid>xyz-test\u003C/id>\n \u003Cproperties>\n \u003Cprojectname.branch>xyz\u003C/projectname.branch>\n \u003Cprojectname.environment>test\u003C/projectname.environment>\n \u003Cprojectname.dbName>dbname\u003C/projectname.dbName>\n \u003Cprojectname.liquibase.url>jdbc:oracle:thin:@192.168.224.234:1521:DBID\u003C/projectname.liquibase.url>\n \u003Cprojectname.liquibase.username>username\u003C/projectname.liquibase.username>\n \u003Cprojectname.liquibase.password>password\u003C/projectname.liquibase.password>\n \u003Cprojectname.liquibase.schemaName>schema\u003C/projectname.liquibase.schemaName>\n \u003Cprojectname.liquibase.changeLogFile>target/classes/path/to/changelog/db.changelog.xml\u003C/projectname.liquibase.changeLogFile>\n \u003C/properties>\n \u003C/profile>\n",[73,3014,3015,3019,3024,3029,3033,3038,3043,3048,3053,3058,3063,3068,3073,3077],{"__ignoreMap":11},[76,3016,3017],{"class":78,"line":79},[76,3018,368],{"emptyLinePlaceholder":23},[76,3020,3021],{"class":78,"line":12},[76,3022,3023],{}," \u003Cprofile>\n",[76,3025,3026],{"class":78,"line":90},[76,3027,3028],{}," \u003Cid>xyz-test\u003C/id>\n",[76,3030,3031],{"class":78,"line":96},[76,3032,2380],{},[76,3034,3035],{"class":78,"line":102},[76,3036,3037],{}," \u003Cprojectname.branch>xyz\u003C/projectname.branch>\n",[76,3039,3040],{"class":78,"line":108},[76,3041,3042],{}," \u003Cprojectname.environment>test\u003C/projectname.environment>\n",[76,3044,3045],{"class":78,"line":114},[76,3046,3047],{}," \u003Cprojectname.dbName>dbname\u003C/projectname.dbName>\n",[76,3049,3050],{"class":78,"line":120},[76,3051,3052],{}," \u003Cprojectname.liquibase.url>jdbc:oracle:thin:@192.168.224.234:1521:DBID\u003C/projectname.liquibase.url>\n",[76,3054,3055],{"class":78,"line":125},[76,3056,3057],{}," \u003Cprojectname.liquibase.username>username\u003C/projectname.liquibase.username>\n",[76,3059,3060],{"class":78,"line":130},[76,3061,3062],{}," \u003Cprojectname.liquibase.password>password\u003C/projectname.liquibase.password>\n",[76,3064,3065],{"class":78,"line":136},[76,3066,3067],{}," \u003Cprojectname.liquibase.schemaName>schema\u003C/projectname.liquibase.schemaName>\n",[76,3069,3070],{"class":78,"line":142},[76,3071,3072],{}," \u003Cprojectname.liquibase.changeLogFile>target/classes/path/to/changelog/db.changelog.xml\u003C/projectname.liquibase.changeLogFile>\n",[76,3074,3075],{"class":78,"line":147},[76,3076,2395],{},[76,3078,3079],{"class":78,"line":152},[76,3080,3081],{}," \u003C/profile>\n",[41,3083,3084],{},"With this config, it uses the property file target/classes/liquibase-test.properties (keep in mind, the file initially\nlies in the folder src/main/resources, but because we build the project before we execute liquibase, it is then located\nunder target/classes/ , with its parameters replaced by our properties).",[41,3086,3087],{},"liquibase-test.properties:",[67,3089,3091],{"className":598,"code":3090,"language":600,"meta":11,"style":11},"changeLogFile=${projectname.liquibase.changeLogFile}\ndriver=oracle.jdbc.OracleDriver\nurl=${projectname.liquibase.url}\nusername=${projectname.liquibase.username}\npassword=${projectname.liquibase.password}\ndefaultSchemaName=${projectname.liquibase.schemaName}\nverbose=true\ndropFirst=false\n",[73,3092,3093,3098,3103,3108,3113,3118,3123,3128],{"__ignoreMap":11},[76,3094,3095],{"class":78,"line":79},[76,3096,3097],{},"changeLogFile=${projectname.liquibase.changeLogFile}\n",[76,3099,3100],{"class":78,"line":12},[76,3101,3102],{},"driver=oracle.jdbc.OracleDriver\n",[76,3104,3105],{"class":78,"line":90},[76,3106,3107],{},"url=${projectname.liquibase.url}\n",[76,3109,3110],{"class":78,"line":96},[76,3111,3112],{},"username=${projectname.liquibase.username}\n",[76,3114,3115],{"class":78,"line":102},[76,3116,3117],{},"password=${projectname.liquibase.password}\n",[76,3119,3120],{"class":78,"line":108},[76,3121,3122],{},"defaultSchemaName=${projectname.liquibase.schemaName}\n",[76,3124,3125],{"class":78,"line":114},[76,3126,3127],{},"verbose=true\n",[76,3129,3130],{"class":78,"line":120},[76,3131,3132],{},"dropFirst=false\n",[41,3134,3135],{},"Here we map our properties from the profiles to the actual liquibase property names and also set a few other liquibase\nconfigs.",[41,3137,3138],{},"For scripts you need to execute in another schema as the one the db user has set as the default schema, we also set the\ndefaultSchemaName property of liquibase (mainly the case, if we execute scripts as the SYSDBA user).",[56,3140,3142],{"id":3141},"execution-conclusion","Execution & Conclusion",[41,3144,3145],{},"Because of the use of maven, we can execute all of the changes from our local machines very easy:",[67,3147,3149],{"className":598,"code":3148,"language":600,"meta":11,"style":11},"mvn clean install -Pxyz-test\n",[73,3150,3151],{"__ignoreMap":11},[76,3152,3153],{"class":78,"line":79},[76,3154,3148],{},[41,3156,3157],{},"If you connect against a remote server, you are even warned with a dialogue that contains the database name, url and\nusername, it wants to execute the scripts on, before the scripts are actually executed. So you can check them again and\nabort the migration if you used the wrong profile.",[41,3159,3160],{},"With this setup we can now add scripts for only one branch, multiple branches, or all branches, without having to worry\nto forget to add one change to a branch and leaving the error unnoticed. Even if we forget to put some file in the\nfolder of one branch, our changelog file is global for all branches! So if we try to execute it the next time, liquibase\nnotices the missing file and informs us about this (and aborts the execution). And because we don’t have different\nfolders for the environments, but only the branches, this gets noticed on the test machines.",[41,3162,3163],{},"Please let us know what you think of our approach and if you know an even better one!",[295,3165,297],{},{"title":11,"searchDepth":12,"depth":12,"links":3167},[3168,3169,3175],{"id":2559,"depth":12,"text":2560},{"id":2603,"depth":12,"text":2604,"children":3170},[3171,3172,3173,3174],{"id":2607,"depth":90,"text":2608},{"id":2806,"depth":90,"text":2807},{"id":2913,"depth":90,"text":2914},{"id":3002,"depth":90,"text":3003},{"id":3141,"depth":12,"text":3142},[1790],"2013-04-12T11:19:55","In this post, we want to show you our Liquibase setup in a larger scale project that we’ve\\nbeen developing for some time now.","https://synyx.de/blog/liquibase-our-setup-in-a-larger-scale-project/",{},"/blog/liquibase-our-setup-in-a-larger-scale-project",{"title":2541,"description":3183},"In this post, we want to show you our Liquibase setup in a larger scale project that we’ve\nbeen developing for some time now.","blog/liquibase-our-setup-in-a-larger-scale-project",[3186,3187,3188,3189],"database","database-change-management","database-migration","liquibase","In this post, we want to show you our Liquibase setup in a larger scale project that we’ve been developing for some time now. Gather Requirements First off, a bit…","Edhd0rsE8wWORJZowAN7nLiXbUivWvpaqbjev0vh3MQ",{"id":3193,"title":3194,"author":3195,"body":3196,"category":3293,"date":3294,"description":3295,"extension":16,"link":3296,"meta":3297,"navigation":23,"path":3298,"seo":3299,"slug":3200,"stem":3300,"tags":3301,"teaser":3309,"__hash__":3310},"blog/blog/android-size-depending-orientation-lock.md","Android: size depending orientation lock",[26],{"type":8,"value":3197,"toc":3291},[3198,3201,3204,3207,3216,3219,3222,3277,3280,3283,3286,3289],[37,3199,3194],{"id":3200},"android-size-depending-orientation-lock",[41,3202,3203],{},"We had a case in an internal app, where on Phones only the Portrait mode should be possible and on Tablets only the\nLandscape mode. So I googled a bit and tried out some things, and here is the solution I found for this problem.",[41,3205,3206],{},"First, in each Activiy in the AndroidManifest (or each Activity that should have this behaviour, but I prefer a\nconsistent behaviour for the whole app), declare the following:",[67,3208,3210],{"className":1124,"code":3209,"language":1126,"meta":11,"style":11},"android:screenOrientation=\"nosensor\"\n",[73,3211,3212],{"__ignoreMap":11},[76,3213,3214],{"class":78,"line":79},[76,3215,3209],{},[41,3217,3218],{},"This will prevent the Activity to switch orientations if the user rotates the device.",[41,3220,3221],{},"Then create a BaseActivity that all your Activities will be extending. In the onCreate we’ll do the other part of the\ntrick.",[67,3223,3225],{"className":226,"code":3224,"language":228,"meta":11,"style":11},"\n@Override\n protected void onCreate(Bundle savedInstanceState) {\n if (getRequestedOrientation() == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE\n && (this.getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) \u003C Configuration.SCREENLAYOUT_SIZE_LARGE) {\n setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);\n }else if((this.getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_LARGE){\n setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);\n }\n super.onCreate(savedInstanceState);\n }\n\n",[73,3226,3227,3231,3236,3240,3245,3250,3255,3260,3265,3269,3273],{"__ignoreMap":11},[76,3228,3229],{"class":78,"line":79},[76,3230,368],{"emptyLinePlaceholder":23},[76,3232,3233],{"class":78,"line":12},[76,3234,3235],{},"@Override\n",[76,3237,3238],{"class":78,"line":90},[76,3239,245],{},[76,3241,3242],{"class":78,"line":96},[76,3243,3244],{}," if (getRequestedOrientation() == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE\n",[76,3246,3247],{"class":78,"line":102},[76,3248,3249],{}," && (this.getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) \u003C Configuration.SCREENLAYOUT_SIZE_LARGE) {\n",[76,3251,3252],{"class":78,"line":108},[76,3253,3254],{}," setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);\n",[76,3256,3257],{"class":78,"line":114},[76,3258,3259],{}," }else if((this.getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_LARGE){\n",[76,3261,3262],{"class":78,"line":120},[76,3263,3264],{}," setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);\n",[76,3266,3267],{"class":78,"line":125},[76,3268,105],{},[76,3270,3271],{"class":78,"line":130},[76,3272,250],{},[76,3274,3275],{"class":78,"line":136},[76,3276,199],{},[41,3278,3279],{},"Here we check for the requested screen orientation and change it according to the screen size of the device. If it’s at\nleast a large (as in Configuration.SCREENLAYOUT_SIZE_LARGE) display, only allow Landscape, otherwise, only allow\nPortrait mode. We need this, because the Activity is normally started with the same orientation the device had on start\nof the Activity.",[41,3281,3282],{},"Now only make sure you have a Landscape layout (res/layout-land) and a Portait layout (res/layout-port) for all\nActivities that haven’t got a layout file that’s used for both orientations.",[41,3284,3285],{},"Also make sure to call the super.onCreate() in the child classes onCreate() first, otherwise it could load the wrong\nlayout!",[41,3287,3288],{},"For now this small solution worked for us, but we don’t have that many different devices. If you encounter any device\nthis doesn’t work with, or encounter some other issuese with it, please let us know!",[295,3290,297],{},{"title":11,"searchDepth":12,"depth":12,"links":3292},[],[1791,1792],"2013-01-18T09:24:34","We had a case in an internal app, where on Phones only the Portrait mode should be possible and on Tablets only the\\nLandscape mode. So I googled a bit and tried out some things, and here is the solution I found for this problem.","https://synyx.de/blog/android-size-depending-orientation-lock/",{},"/blog/android-size-depending-orientation-lock",{"title":3194,"description":3203},"blog/android-size-depending-orientation-lock",[314,3302,3303,3304,3305,3306,3307,3308],"landscape","layout","lock","orientation","portrait","screen-size","tablet","We had a case in an internal app, where on Phones only the Portrait mode should be possible and on Tablets only the Landscape mode. So I googled a bit…","jixMMHEhK8mmvfHwd351p6yWAaNQ9FQxySWBOP7RgxY",{"id":3312,"title":3313,"author":3314,"body":3315,"category":4479,"date":4480,"description":3322,"extension":16,"link":4481,"meta":4482,"navigation":23,"path":4483,"seo":4484,"slug":3319,"stem":4485,"tags":4486,"teaser":4493,"__hash__":4494},"blog/blog/a-small-look-into-google-cloud-messages.md","A small look into Google Cloud Messages",[26],{"type":8,"value":3316,"toc":4475},[3317,3320,3323,3331,3336,3340,3343,3346,3415,3418,3477,3480,3489,3492,3495,3498,3526,3529,3538,3541,3556,3559,3629,3632,3635,3732,3735,3738,3741,3745,3748,3755,3758,3889,3892,4348,4351,4446,4449,4452,4458,4461,4464,4472],[37,3318,3313],{"id":3319},"a-small-look-into-google-cloud-messages",[41,3321,3322],{},"Within the scope of some Android R&D I took a look at Google’s Cloud Message Service, GCM.",[41,3324,3325,3326,3330],{},"Well, the starter guide at",[45,3327,3328],{"href":3328,"rel":3329},"http://developer.android.com/google/gcm/gs.html",[49]," is almost all you need to get started, so\nI’ll explain my setup and some further instructions for a small test case.",[41,3332,3333],{},[1159,3334,3335],{},"In case you already decided to setup GCM for yourself: make sure to do the guide above until you reach the ‘Writing the\nAndroid Application’ part.",[56,3337,3339],{"id":3338},"the-android-application","The Android Application",[41,3341,3342],{},"Start with creating a new Android Project with a minimum Android API version of 8 (2.2). I just named it\n‘CloudMessageTest’ with the MainActivity ‘CloudMessageTestActivity’. After you’ve done that, create a folder named ‘lib’\nin your app’s root directory and copy the android GCM library from\nYOUR_SDK_ROOT/extras/google/gcm/gcm-client/dist/gcm.jar to the created lib folder. Next, Add the library to your build\npath (go into the Project properties -> Java Build Path -> Tab Libraries and select add JARs to add the gcm.jar).",[41,3344,3345],{},"For GCM to work, we need to add some permissions to our AndroidManifest:",[67,3347,3349],{"className":1124,"code":3348,"language":1126,"meta":11,"style":11},"\n\u003Cpermission\n android:name=\"com.synyx.cloudmessagetest.permission.C2D_MESSAGE\"\n android:protectionLevel=\"signature\"/>\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",[73,3350,3351,3355,3360,3365,3370,3375,3380,3385,3390,3395,3400,3405,3410],{"__ignoreMap":11},[76,3352,3353],{"class":78,"line":79},[76,3354,368],{"emptyLinePlaceholder":23},[76,3356,3357],{"class":78,"line":12},[76,3358,3359],{},"\u003Cpermission\n",[76,3361,3362],{"class":78,"line":90},[76,3363,3364],{}," android:name=\"com.synyx.cloudmessagetest.permission.C2D_MESSAGE\"\n",[76,3366,3367],{"class":78,"line":96},[76,3368,3369],{}," android:protectionLevel=\"signature\"/>\n",[76,3371,3372],{"class":78,"line":102},[76,3373,3374],{},"\u003Cuses-permission android:name=\"com.synyx.cloudmessagetest.permission.C2D_MESSAGE\"/>\n",[76,3376,3377],{"class":78,"line":108},[76,3378,3379],{}," \u003C!-- App receives GCM messages. -->\n",[76,3381,3382],{"class":78,"line":114},[76,3383,3384],{},"\u003Cuses-permission android:name=\"com.google.android.c2dm.permission.RECEIVE\"/>\n",[76,3386,3387],{"class":78,"line":120},[76,3388,3389],{}," \u003C!-- GCM connects to Google Services. -->\n",[76,3391,3392],{"class":78,"line":125},[76,3393,3394],{},"\u003Cuses-permission android:name=\"android.permission.INTERNET\"/>\n",[76,3396,3397],{"class":78,"line":130},[76,3398,3399],{}," \u003C!-- GCM requires a Google account. -->\n",[76,3401,3402],{"class":78,"line":136},[76,3403,3404],{},"\u003Cuses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>\n",[76,3406,3407],{"class":78,"line":142},[76,3408,3409],{}," \u003C!-- Keeps the processor from sleeping when a message is received. -->\n",[76,3411,3412],{"class":78,"line":147},[76,3413,3414],{},"\u003Cuses-permission android:name=\"android.permission.WAKE_LOCK\"/>\n",[41,3416,3417],{},"And declare a GCM broadcast receiver within the application tag:",[67,3419,3421],{"className":1124,"code":3420,"language":1126,"meta":11,"style":11},"\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",[73,3422,3423,3427,3432,3437,3442,3447,3452,3457,3462,3467,3472],{"__ignoreMap":11},[76,3424,3425],{"class":78,"line":79},[76,3426,368],{"emptyLinePlaceholder":23},[76,3428,3429],{"class":78,"line":12},[76,3430,3431],{},"\u003Creceiver\n",[76,3433,3434],{"class":78,"line":90},[76,3435,3436],{}," android:name=\"com.google.android.gcm.GCMBroadcastReceiver\"\n",[76,3438,3439],{"class":78,"line":96},[76,3440,3441],{}," android:permission=\"com.google.android.c2dm.permission.SEND\">\n",[76,3443,3444],{"class":78,"line":102},[76,3445,3446],{}," \u003Cintent-filter>\n",[76,3448,3449],{"class":78,"line":108},[76,3450,3451],{}," \u003Caction android:name=\"com.google.android.c2dm.intent.RECEIVE\"/>\n",[76,3453,3454],{"class":78,"line":114},[76,3455,3456],{}," \u003Caction android:name=\"com.google.android.c2dm.intent.REGISTRATION\"/>\n",[76,3458,3459],{"class":78,"line":120},[76,3460,3461],{}," \u003Ccategory android:name=\"com.synyx.cloudmessagetest\"/>\n",[76,3463,3464],{"class":78,"line":125},[76,3465,3466],{}," \u003C/intent-filter>\n",[76,3468,3469],{"class":78,"line":130},[76,3470,3471],{},"\u003C/receiver>\n",[76,3473,3474],{"class":78,"line":136},[76,3475,3476],{},"\u003Cservice android:name=\".GCMIntentService\"/>\n",[41,3478,3479],{},"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:",[67,3481,3483],{"className":226,"code":3482,"language":228,"meta":11,"style":11},"public class GCMIntentService extends GCMBaseIntentService {\n",[73,3484,3485],{"__ignoreMap":11},[76,3486,3487],{"class":78,"line":79},[76,3488,3482],{},[41,3490,3491],{},"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.",[41,3493,3494],{},"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.",[41,3496,3497],{},"First in the onMessage() method, we need to get access to the Main Thread of our App.",[67,3499,3501],{"className":226,"code":3500,"language":228,"meta":11,"style":11},"Handler h = new Handler(Looper.getMainLooper());\nh.post(new Runnable() {\n public void run() {\n }\n}\n",[73,3502,3503,3508,3513,3518,3522],{"__ignoreMap":11},[76,3504,3505],{"class":78,"line":79},[76,3506,3507],{},"Handler h = new Handler(Looper.getMainLooper());\n",[76,3509,3510],{"class":78,"line":12},[76,3511,3512],{},"h.post(new Runnable() {\n",[76,3514,3515],{"class":78,"line":90},[76,3516,3517],{}," public void run() {\n",[76,3519,3520],{"class":78,"line":96},[76,3521,199],{},[76,3523,3524],{"class":78,"line":102},[76,3525,287],{},[41,3527,3528],{},"In the run() method, we get us an Intent from our MainActivity",[67,3530,3532],{"className":226,"code":3531,"language":228,"meta":11,"style":11},"Intent notificationIntent = new Intent(context, CloudMessageTestActivity.class);\n",[73,3533,3534],{"__ignoreMap":11},[76,3535,3536],{"class":78,"line":79},[76,3537,3531],{},[41,3539,3540],{},"And then wrap it with a PendingIntent for the NotificationBuilder",[67,3542,3544],{"className":226,"code":3543,"language":228,"meta":11,"style":11},"PendingIntent pendingIntent = PendingIntent.getActivity(context, 0,\n notificationIntent, 0);\n",[73,3545,3546,3551],{"__ignoreMap":11},[76,3547,3548],{"class":78,"line":79},[76,3549,3550],{},"PendingIntent pendingIntent = PendingIntent.getActivity(context, 0,\n",[76,3552,3553],{"class":78,"line":12},[76,3554,3555],{}," notificationIntent, 0);\n",[41,3557,3558],{},"Finally, use the NotificationBuilder to create and send the notification",[67,3560,3562],{"className":226,"code":3561,"language":228,"meta":11,"style":11},"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",[73,3563,3564,3569,3574,3579,3584,3589,3594,3599,3604,3609,3614,3619,3624],{"__ignoreMap":11},[76,3565,3566],{"class":78,"line":79},[76,3567,3568],{},"NotificationCompat.Builder builder = new NotificationCompat.Builder(\n",[76,3570,3571],{"class":78,"line":12},[76,3572,3573],{}," context);\n",[76,3575,3576],{"class":78,"line":90},[76,3577,3578],{},"builder.setContentIntent(pendingIntent);\n",[76,3580,3581],{"class":78,"line":96},[76,3582,3583],{},"builder.setAutoCancel(true);\n",[76,3585,3586],{"class":78,"line":102},[76,3587,3588],{},"builder.setSmallIcon(R.drawable.ic_launcher);\n",[76,3590,3591],{"class":78,"line":108},[76,3592,3593],{},"//this is added on the server side\n",[76,3595,3596],{"class":78,"line":114},[76,3597,3598],{},"String text = intent.getStringExtra(\"text\");\n",[76,3600,3601],{"class":78,"line":120},[76,3602,3603],{},"builder.setContentText(text);\n",[76,3605,3606],{"class":78,"line":125},[76,3607,3608],{},"builder.setContentTitle(\"New message from the cloud!\");\n",[76,3610,3611],{"class":78,"line":130},[76,3612,3613],{},"Notification noti = builder.build();\n",[76,3615,3616],{"class":78,"line":136},[76,3617,3618],{},"NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);\n",[76,3620,3621],{"class":78,"line":142},[76,3622,3623],{},"//just set the mId to 1, because we don't care about it in this case\n",[76,3625,3626],{"class":78,"line":147},[76,3627,3628],{},"mNotificationManager.notify(1, noti);\n",[41,3630,3631],{},"That’ it with the GCMIntentService.",[41,3633,3634],{},"Now we let the app register itself with GCM in our MainActivity, which is fairly easy:",[67,3636,3638],{"className":226,"code":3637,"language":228,"meta":11,"style":11},"//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",[73,3639,3640,3645,3650,3654,3659,3664,3669,3674,3679,3684,3689,3694,3699,3704,3709,3714,3719,3723,3728],{"__ignoreMap":11},[76,3641,3642],{"class":78,"line":79},[76,3643,3644],{},"//set your senderId from the API here!\n",[76,3646,3647],{"class":78,"line":12},[76,3648,3649],{}," private static final String SENDER_ID = \"1234567890\";\n",[76,3651,3652],{"class":78,"line":90},[76,3653,3235],{},[76,3655,3656],{"class":78,"line":96},[76,3657,3658],{},"protected void onCreate(Bundle savedInstanceState) {\n",[76,3660,3661],{"class":78,"line":102},[76,3662,3663],{}," GCMRegistrar.checkDevice(this);\n",[76,3665,3666],{"class":78,"line":108},[76,3667,3668],{}," GCMRegistrar.checkManifest(this);\n",[76,3670,3671],{"class":78,"line":114},[76,3672,3673],{}," final String regId = GCMRegistrar.getRegistrationId(this);\n",[76,3675,3676],{"class":78,"line":120},[76,3677,3678],{}," // if we don't have a regId yet, register at gcm\n",[76,3680,3681],{"class":78,"line":125},[76,3682,3683],{}," if (regId.equals(\"\")) {\n",[76,3685,3686],{"class":78,"line":130},[76,3687,3688],{}," GCMRegistrar.register(this, SENDER_ID);\n",[76,3690,3691],{"class":78,"line":136},[76,3692,3693],{}," Toast.makeText(getApplicationContext(), \"Registered GCM!\", Toast.LENGTH_LONG).show();\n",[76,3695,3696],{"class":78,"line":142},[76,3697,3698],{}," // just log the registrationId for this test case.\n",[76,3700,3701],{"class":78,"line":147},[76,3702,3703],{}," Log.i(this.getClass().getName(), \"id: \" + GCMRegistrar.getRegistrationId(this));\n",[76,3705,3706],{"class":78,"line":152},[76,3707,3708],{}," } else {\n",[76,3710,3711],{"class":78,"line":158},[76,3712,3713],{}," Log.i(this.getClass().getName(), \"Already registered\");\n",[76,3715,3716],{"class":78,"line":164},[76,3717,3718],{}," Toast.makeText(getApplicationContext(), \"Already registered at GCM!\", Toast.LENGTH_LONG).show();\n",[76,3720,3721],{"class":78,"line":169},[76,3722,3703],{},[76,3724,3725],{"class":78,"line":174},[76,3726,3727],{}," }\n",[76,3729,3730],{"class":78,"line":180},[76,3731,287],{},[41,3733,3734],{},"Don’t forget to replace the SENDER_ID with yours!",[41,3736,3737],{},"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.",[41,3739,3740],{},"With this, we finished our small app and can begin implementing the server part.",[56,3742,3744],{"id":3743},"the-web-application","The Web Application",[41,3746,3747],{},"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.",[41,3749,3750,3751,1166],{},"Instead of just copying the GCM server library into the project, I searched a bit and found someone, who created a\nrepository for\nit. (",[45,3752,3753],{"href":3753,"rel":3754},"https://github.com/slorber/gcm-server-repository",[49],[41,3756,3757],{},"We start with creating the Sender class, which isn’t that hard either.",[67,3759,3761],{"className":226,"code":3760,"language":228,"meta":11,"style":11},"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",[73,3762,3763,3768,3773,3778,3783,3788,3793,3798,3803,3808,3813,3818,3823,3828,3833,3838,3843,3848,3853,3858,3863,3868,3872,3876,3881,3885],{"__ignoreMap":11},[76,3764,3765],{"class":78,"line":79},[76,3766,3767],{},"public class GCMSender {\n",[76,3769,3770],{"class":78,"line":12},[76,3771,3772],{}," public String apiKey = null;\n",[76,3774,3775],{"class":78,"line":90},[76,3776,3777],{}," public GCMSender(String apiKey) {\n",[76,3779,3780],{"class":78,"line":96},[76,3781,3782],{}," this.apiKey = apiKey;\n",[76,3784,3785],{"class":78,"line":102},[76,3786,3787],{}," }\n",[76,3789,3790],{"class":78,"line":108},[76,3791,3792],{}," public String send(String text, String id) throws IOException {\n",[76,3794,3795],{"class":78,"line":114},[76,3796,3797],{}," Sender sender = new Sender(apiKey);\n",[76,3799,3800],{"class":78,"line":120},[76,3801,3802],{}," Builder builder = new Message.Builder();\n",[76,3804,3805],{"class":78,"line":125},[76,3806,3807],{}," builder.addData(\"text\", text);\n",[76,3809,3810],{"class":78,"line":130},[76,3811,3812],{}," Result result = sender.send(builder.build(), id, 5);\n",[76,3814,3815],{"class":78,"line":136},[76,3816,3817],{}," if (result.getMessageId() != null) {\n",[76,3819,3820],{"class":78,"line":142},[76,3821,3822],{}," String canonicalRegId = result.getCanonicalRegistrationId();\n",[76,3824,3825],{"class":78,"line":147},[76,3826,3827],{}," if (canonicalRegId != null) {\n",[76,3829,3830],{"class":78,"line":152},[76,3831,3832],{}," // same device has more than on registration ID: update database\n",[76,3834,3835],{"class":78,"line":158},[76,3836,3837],{}," return \"same device has more than on registration ID: update database\";\n",[76,3839,3840],{"class":78,"line":164},[76,3841,3842],{}," }\n",[76,3844,3845],{"class":78,"line":169},[76,3846,3847],{}," } else {\n",[76,3849,3850],{"class":78,"line":174},[76,3851,3852],{}," String error = result.getErrorCodeName();\n",[76,3854,3855],{"class":78,"line":180},[76,3856,3857],{}," if (error.equals(Constants.ERROR_NOT_REGISTERED)) {\n",[76,3859,3860],{"class":78,"line":186},[76,3861,3862],{}," // application has been removed from device - unregister database\n",[76,3864,3865],{"class":78,"line":191},[76,3866,3867],{}," return \"application has been removed from device - unregister database\";\n",[76,3869,3870],{"class":78,"line":196},[76,3871,3842],{},[76,3873,3874],{"class":78,"line":558},[76,3875,3727],{},[76,3877,3878],{"class":78,"line":563},[76,3879,3880],{}," return null;\n",[76,3882,3883],{"class":78,"line":568},[76,3884,3787],{},[76,3886,3887],{"class":78,"line":573},[76,3888,287],{},[41,3890,3891],{},"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:",[67,3893,3895],{"className":1248,"code":3894,"language":1250,"meta":11,"style":11},"\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",[73,3896,3897,3909,3914,3929,3937,3946,3975,3982,3992,4002,4012,4022,4027,4041,4050,4059,4071,4088,4111,4120,4135,4150,4155,4169,4178,4186,4203,4224,4261,4279,4309,4324,4332,4340],{"__ignoreMap":11},[76,3898,3899,3901,3904,3906],{"class":78,"line":79},[76,3900,1258],{"class":1261},[76,3902,3903],{"class":1257},"%@ taglib prefix=\"c\" uri=\"http://java.sun.com/jsp/jstl/core\" %> ",[76,3905,1258],{"class":1261},[76,3907,3908],{"class":1257},"%@page\n",[76,3910,3911],{"class":78,"line":12},[76,3912,3913],{"class":1257},"contentType=\"text/html\" pageEncoding=\"UTF-8\"%>\n",[76,3915,3916,3919,3923,3927],{"class":78,"line":90},[76,3917,3918],{"class":1257},"\u003C!",[76,3920,3922],{"class":3921},"s9eBZ","DOCTYPE",[76,3924,3926],{"class":3925},"sScJk"," html",[76,3928,1265],{"class":1257},[76,3930,3931,3933,3935],{"class":78,"line":96},[76,3932,1258],{"class":1257},[76,3934,1250],{"class":3921},[76,3936,1265],{"class":1257},[76,3938,3939,3941,3944],{"class":78,"line":102},[76,3940,1270],{"class":1257},[76,3942,3943],{"class":3921},"head",[76,3945,1265],{"class":1257},[76,3947,3948,3951,3954,3957,3960,3964,3967,3969,3972],{"class":78,"line":108},[76,3949,3950],{"class":1257}," \u003C",[76,3952,3953],{"class":3921},"meta",[76,3955,3956],{"class":3925}," http-equiv",[76,3958,3959],{"class":1257},"=",[76,3961,3963],{"class":3962},"sZZnC","\"Content-Type\"",[76,3965,3966],{"class":3925}," content",[76,3968,3959],{"class":1257},[76,3970,3971],{"class":3962},"\"text/html; charset=UTF-8\"",[76,3973,3974],{"class":1257}," />\n",[76,3976,3977,3979],{"class":78,"line":114},[76,3978,3950],{"class":1257},[76,3980,3981],{"class":3921},"link\n",[76,3983,3984,3987,3989],{"class":78,"line":120},[76,3985,3986],{"class":3925}," rel",[76,3988,3959],{"class":1257},[76,3990,3991],{"class":3962},"\"stylesheet\"\n",[76,3993,3994,3997,3999],{"class":78,"line":125},[76,3995,3996],{"class":3925}," type",[76,3998,3959],{"class":1257},[76,4000,4001],{"class":3962},"\"text/css\"\n",[76,4003,4004,4007,4009],{"class":78,"line":130},[76,4005,4006],{"class":3925}," href",[76,4008,3959],{"class":1257},[76,4010,4011],{"class":3962},"\"/GCMTestServer/frontend_resources/style.css\"\n",[76,4013,4014,4017,4019],{"class":78,"line":136},[76,4015,4016],{"class":3925}," media",[76,4018,3959],{"class":1257},[76,4020,4021],{"class":3962},"\"screen\"\n",[76,4023,4024],{"class":78,"line":142},[76,4025,4026],{"class":1257}," />\n",[76,4028,4029,4031,4034,4037,4039],{"class":78,"line":147},[76,4030,3950],{"class":1257},[76,4032,4033],{"class":3921},"title",[76,4035,4036],{"class":1257},">GCM Sender\u003C/",[76,4038,4033],{"class":3921},[76,4040,1265],{"class":1257},[76,4042,4043,4046,4048],{"class":78,"line":152},[76,4044,4045],{"class":1257}," \u003C/",[76,4047,3943],{"class":3921},[76,4049,1265],{"class":1257},[76,4051,4052,4054,4057],{"class":78,"line":158},[76,4053,1270],{"class":1257},[76,4055,4056],{"class":3921},"body",[76,4058,1265],{"class":1257},[76,4060,4061,4063,4065,4067,4069],{"class":78,"line":164},[76,4062,3950],{"class":1257},[76,4064,37],{"class":3921},[76,4066,4036],{"class":1257},[76,4068,37],{"class":3921},[76,4070,1265],{"class":1257},[76,4072,4073,4075,4078,4081,4083,4086],{"class":78,"line":169},[76,4074,3950],{"class":1257},[76,4076,4077],{"class":1261},"c:if",[76,4079,4080],{"class":3925}," test",[76,4082,3959],{"class":1257},[76,4084,4085],{"class":3962},"\"${success == true}\"",[76,4087,1265],{"class":1257},[76,4089,4090,4093,4096,4099,4101,4104,4107,4109],{"class":78,"line":174},[76,4091,4092],{"class":1257}," \u003C",[76,4094,4095],{"class":3921},"div",[76,4097,4098],{"class":3925}," class",[76,4100,3959],{"class":1257},[76,4102,4103],{"class":3962},"\"success\"",[76,4105,4106],{"class":1257},">sending successful!\u003C/",[76,4108,4095],{"class":3921},[76,4110,1265],{"class":1257},[76,4112,4113,4116,4118],{"class":78,"line":180},[76,4114,4115],{"class":1257}," \u003C/",[76,4117,4077],{"class":1261},[76,4119,1265],{"class":1257},[76,4121,4122,4124,4126,4128,4130,4133],{"class":78,"line":186},[76,4123,3950],{"class":1257},[76,4125,4077],{"class":1261},[76,4127,4080],{"class":3925},[76,4129,3959],{"class":1257},[76,4131,4132],{"class":3962},"\"${error == true}\"",[76,4134,1265],{"class":1257},[76,4136,4137,4139,4141,4143,4145,4148],{"class":78,"line":191},[76,4138,4092],{"class":1257},[76,4140,4095],{"class":3921},[76,4142,4098],{"class":3925},[76,4144,3959],{"class":1257},[76,4146,4147],{"class":3962},"\"error\"",[76,4149,1265],{"class":1257},[76,4151,4152],{"class":78,"line":196},[76,4153,4154],{"class":1257}," Error at sending!\n",[76,4156,4157,4160,4162,4165,4167],{"class":78,"line":558},[76,4158,4159],{"class":1257}," \u003C",[76,4161,41],{"class":3921},[76,4163,4164],{"class":1257},">${errormessage}\u003C/",[76,4166,41],{"class":3921},[76,4168,1265],{"class":1257},[76,4170,4171,4174,4176],{"class":78,"line":563},[76,4172,4173],{"class":1257}," \u003C/",[76,4175,4095],{"class":3921},[76,4177,1265],{"class":1257},[76,4179,4180,4182,4184],{"class":78,"line":568},[76,4181,4115],{"class":1257},[76,4183,4077],{"class":1261},[76,4185,1265],{"class":1257},[76,4187,4188,4190,4193,4196,4198,4201],{"class":78,"line":573},[76,4189,3950],{"class":1257},[76,4191,4192],{"class":3921},"form",[76,4194,4195],{"class":3925}," method",[76,4197,3959],{"class":1257},[76,4199,4200],{"class":3962},"\"POST\"",[76,4202,1265],{"class":1257},[76,4204,4205,4207,4210,4213,4215,4218,4221],{"class":78,"line":579},[76,4206,4092],{"class":1257},[76,4208,4209],{"class":3921},"label",[76,4211,4212],{"class":3925}," for",[76,4214,3959],{"class":1257},[76,4216,4217],{"class":3962},"\"text\"",[76,4219,4220],{"class":1257},">Text\u003C/",[76,4222,4223],{"class":3921},"label\n",[76,4225,4226,4229,4232,4235,4237,4239,4242,4244,4246,4249,4251,4253,4256,4259],{"class":78,"line":585},[76,4227,4228],{"class":1257}," >\u003C",[76,4230,4231],{"class":3921},"input",[76,4233,4234],{"class":3925}," name",[76,4236,3959],{"class":1257},[76,4238,4217],{"class":3962},[76,4240,4241],{"class":3925}," id",[76,4243,3959],{"class":1257},[76,4245,4217],{"class":3962},[76,4247,4248],{"class":3925}," type",[76,4250,3959],{"class":1257},[76,4252,4217],{"class":3962},[76,4254,4255],{"class":1257}," />\u003C",[76,4257,4258],{"class":3921},"br",[76,4260,3974],{"class":1257},[76,4262,4263,4265,4267,4269,4271,4274,4277],{"class":78,"line":590},[76,4264,4092],{"class":1257},[76,4266,4209],{"class":3921},[76,4268,4212],{"class":3925},[76,4270,3959],{"class":1257},[76,4272,4273],{"class":3962},"\"id\"",[76,4275,4276],{"class":1257},">Registration-Id\u003C/",[76,4278,4223],{"class":3921},[76,4280,4281,4283,4285,4287,4289,4291,4293,4295,4297,4299,4301,4303,4305,4307],{"class":78,"line":1620},[76,4282,4228],{"class":1257},[76,4284,4231],{"class":3921},[76,4286,4234],{"class":3925},[76,4288,3959],{"class":1257},[76,4290,4273],{"class":3962},[76,4292,4241],{"class":3925},[76,4294,3959],{"class":1257},[76,4296,4273],{"class":3962},[76,4298,4248],{"class":3925},[76,4300,3959],{"class":1257},[76,4302,4217],{"class":3962},[76,4304,4255],{"class":1257},[76,4306,4258],{"class":3921},[76,4308,3974],{"class":1257},[76,4310,4311,4313,4315,4317,4319,4322],{"class":78,"line":1625},[76,4312,4092],{"class":1257},[76,4314,4231],{"class":3921},[76,4316,4248],{"class":3925},[76,4318,3959],{"class":1257},[76,4320,4321],{"class":3962},"\"submit\"",[76,4323,3974],{"class":1257},[76,4325,4326,4328,4330],{"class":78,"line":1630},[76,4327,4115],{"class":1257},[76,4329,4192],{"class":3921},[76,4331,1265],{"class":1257},[76,4333,4334,4336,4338],{"class":78,"line":1636},[76,4335,4045],{"class":1257},[76,4337,4056],{"class":3921},[76,4339,1265],{"class":1257},[76,4341,4342,4344,4346],{"class":78,"line":1641},[76,4343,1304],{"class":1257},[76,4345,1250],{"class":3921},[76,4347,1265],{"class":1257},[41,4349,4350],{},"In the corresponding Controller to process the request, send the message:",[67,4352,4354],{"className":226,"code":4353,"language":228,"meta":11,"style":11}," @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",[73,4355,4356,4361,4366,4371,4376,4381,4386,4391,4396,4401,4406,4410,4415,4420,4424,4428,4433,4437,4442],{"__ignoreMap":11},[76,4357,4358],{"class":78,"line":79},[76,4359,4360],{}," @RequestMapping(value = \"send\", method = RequestMethod.POST)\n",[76,4362,4363],{"class":78,"line":12},[76,4364,4365],{}," public String send(Model model,\n",[76,4367,4368],{"class":78,"line":90},[76,4369,4370],{}," @RequestParam(\"text\") String text,\n",[76,4372,4373],{"class":78,"line":96},[76,4374,4375],{}," @RequestParam(\"id\") String id) {\n",[76,4377,4378],{"class":78,"line":102},[76,4379,4380],{}," String error = null;\n",[76,4382,4383],{"class":78,"line":108},[76,4384,4385],{}," try {\n",[76,4387,4388],{"class":78,"line":114},[76,4389,4390],{}," error = gcmSender.send(text, id);\n",[76,4392,4393],{"class":78,"line":120},[76,4394,4395],{}," } catch (IOException ex) {\n",[76,4397,4398],{"class":78,"line":125},[76,4399,4400],{}," model.addAttribute(\"error\", true);\n",[76,4402,4403],{"class":78,"line":130},[76,4404,4405],{}," model.addAttribute(\"errormessage\", ex.getMessage());\n",[76,4407,4408],{"class":78,"line":136},[76,4409,105],{},[76,4411,4412],{"class":78,"line":142},[76,4413,4414],{}," if (error == null) {\n",[76,4416,4417],{"class":78,"line":147},[76,4418,4419],{}," model.addAttribute(\"success\", true);\n",[76,4421,4422],{"class":78,"line":152},[76,4423,735],{},[76,4425,4426],{"class":78,"line":158},[76,4427,4400],{},[76,4429,4430],{"class":78,"line":164},[76,4431,4432],{}," model.addAttribute(\"errormessage\", error);\n",[76,4434,4435],{"class":78,"line":169},[76,4436,105],{},[76,4438,4439],{"class":78,"line":174},[76,4440,4441],{}," return \"sender\";\n",[76,4443,4444],{"class":78,"line":180},[76,4445,199],{},[41,4447,4448],{},"Now we are ready to test it!",[41,4450,4451],{},"Start the app, copy the RegistrationId from the Logs, start the Server, enter the RegistrationId and a small text, and\nthere you go:",[41,4453,4454],{},[213,4455],{"alt":4456,"src":4457},"\"notification\"","https://media.synyx.de/uploads//2012/12/notification-300x221.jpg",[41,4459,4460],{},"To get a better understanding, you can also read the rest of the starting guide and the other documentation.",[41,4462,4463],{},"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 😛",[41,4465,4466,4467],{},"As promised, here are the full sources:",[45,4468,4471],{"href":4469,"rel":4470},"https://media.synyx.de/uploads//2012/12/CloudMessageTest.zip",[49],"CloudMessageTest",[295,4473,4474],{},"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":11,"searchDepth":12,"depth":12,"links":4476},[4477,4478],{"id":3338,"depth":12,"text":3339},{"id":3743,"depth":12,"text":3744},[1791,1792],"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":3313,"description":3322},"blog/a-small-look-into-google-cloud-messages",[314,4487,4488,4489,4490,4491,4492],"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":4496,"title":4497,"author":4498,"body":4499,"category":5149,"date":5150,"description":5151,"extension":16,"link":5152,"meta":5153,"navigation":23,"path":5154,"seo":5155,"slug":4503,"stem":5156,"tags":5157,"teaser":5165,"__hash__":5166},"blog/blog/android-expandable-listview.md","Android: Expandable ListView",[26],{"type":8,"value":4500,"toc":5147},[4501,4504,4507,4510,4697,4700,4765,4768,4829,4832,4835,4844,4847,4850,4887,4890,4905,4908,4956,4959,5030,5033,5081,5084,5109,5112,5115,5127,5137,5145],[37,4502,4497],{"id":4503},"android-expandable-listview",[41,4505,4506],{},"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.",[41,4508,4509],{},"To achieve this goal, we first need to implement a basic Adapter that provides our ListView with the entries:",[67,4511,4513],{"className":226,"code":4512,"language":228,"meta":11,"style":11},"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",[73,4514,4515,4520,4525,4530,4535,4540,4544,4548,4553,4558,4562,4566,4571,4576,4581,4586,4590,4594,4599,4604,4608,4612,4617,4622,4627,4632,4637,4641,4646,4650,4655,4660,4665,4670,4674,4679,4684,4689,4693],{"__ignoreMap":11},[76,4516,4517],{"class":78,"line":79},[76,4518,4519],{},"private class ExpandableListAdapter extends BaseAdapter {\n",[76,4521,4522],{"class":78,"line":12},[76,4523,4524],{}," private List entries;\n",[76,4526,4527],{"class":78,"line":90},[76,4528,4529],{}," private Context context;\n",[76,4531,4532],{"class":78,"line":96},[76,4533,4534],{}," public ExpandableListAdapter(List entries) {\n",[76,4536,4537],{"class":78,"line":102},[76,4538,4539],{}," this.entries = entries;\n",[76,4541,4542],{"class":78,"line":108},[76,4543,199],{},[76,4545,4546],{"class":78,"line":114},[76,4547,240],{},[76,4549,4550],{"class":78,"line":120},[76,4551,4552],{}," public int getCount() {\n",[76,4554,4555],{"class":78,"line":125},[76,4556,4557],{}," return entries.size();\n",[76,4559,4560],{"class":78,"line":130},[76,4561,199],{},[76,4563,4564],{"class":78,"line":136},[76,4565,240],{},[76,4567,4568],{"class":78,"line":142},[76,4569,4570],{}," public Object getItem(int position) {\n",[76,4572,4573],{"class":78,"line":147},[76,4574,4575],{}," if (position >= 0 && position \u003C entries.size())\n",[76,4577,4578],{"class":78,"line":152},[76,4579,4580],{}," return this.entries.get(position);\n",[76,4582,4583],{"class":78,"line":158},[76,4584,4585],{}," return null;\n",[76,4587,4588],{"class":78,"line":164},[76,4589,199],{},[76,4591,4592],{"class":78,"line":169},[76,4593,240],{},[76,4595,4596],{"class":78,"line":174},[76,4597,4598],{}," public long getItemId(int position) {\n",[76,4600,4601],{"class":78,"line":180},[76,4602,4603],{}," return position;\n",[76,4605,4606],{"class":78,"line":186},[76,4607,199],{},[76,4609,4610],{"class":78,"line":191},[76,4611,240],{},[76,4613,4614],{"class":78,"line":196},[76,4615,4616],{}," public View getView(int position, View convertView, ViewGroup parent) {\n",[76,4618,4619],{"class":78,"line":558},[76,4620,4621],{}," View view = null;\n",[76,4623,4624],{"class":78,"line":563},[76,4625,4626],{}," //reuse the convertView\n",[76,4628,4629],{"class":78,"line":568},[76,4630,4631],{}," if (convertView == null) {\n",[76,4633,4634],{"class":78,"line":573},[76,4635,4636],{}," view = getLayoutInflater().inflate(R.layout.row, null);\n",[76,4638,4639],{"class":78,"line":579},[76,4640,735],{},[76,4642,4643],{"class":78,"line":585},[76,4644,4645],{}," view = convertView;\n",[76,4647,4648],{"class":78,"line":590},[76,4649,105],{},[76,4651,4652],{"class":78,"line":1620},[76,4653,4654],{}," String entry = entries.get(position);\n",[76,4656,4657],{"class":78,"line":1625},[76,4658,4659],{}," view.setTag(position);\n",[76,4661,4662],{"class":78,"line":1630},[76,4663,4664],{}," fillView(view, entry);\n",[76,4666,4667],{"class":78,"line":1636},[76,4668,4669],{}," return view;\n",[76,4671,4672],{"class":78,"line":1641},[76,4673,199],{},[76,4675,4676],{"class":78,"line":1647},[76,4677,4678],{}," private void fillView(View view, String entry) {\n",[76,4680,4681],{"class":78,"line":1652},[76,4682,4683],{}," TextView text = (TextView) view.findViewById(R.id.text);\n",[76,4685,4686],{"class":78,"line":1657},[76,4687,4688],{}," text.setText(entry);\n",[76,4690,4691],{"class":78,"line":1662},[76,4692,199],{},[76,4694,4695],{"class":78,"line":1667},[76,4696,287],{},[41,4698,4699],{},"For this example, I used a simple view for the rows, with just a TextView in it:",[67,4701,4703],{"className":1124,"code":4702,"language":1126,"meta":11,"style":11},"\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",[73,4704,4705,4710,4715,4720,4725,4730,4735,4740,4745,4750,4755,4760],{"__ignoreMap":11},[76,4706,4707],{"class":78,"line":79},[76,4708,4709],{},"\u003C?xml version=\"1.0\" encoding=\"utf-8\"?>\n",[76,4711,4712],{"class":78,"line":12},[76,4713,4714],{},"\u003CLinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n",[76,4716,4717],{"class":78,"line":90},[76,4718,4719],{}," android:layout_width=\"match_parent\"\n",[76,4721,4722],{"class":78,"line":96},[76,4723,4724],{}," android:layout_height=\"match_parent\"\n",[76,4726,4727],{"class":78,"line":102},[76,4728,4729],{}," android:orientation=\"vertical\">\n",[76,4731,4732],{"class":78,"line":108},[76,4733,4734],{}," \u003CTextView\n",[76,4736,4737],{"class":78,"line":114},[76,4738,4739],{}," android:id=\"@+id/text\"\n",[76,4741,4742],{"class":78,"line":120},[76,4743,4744],{}," android:layout_width=\"match_parent\"\n",[76,4746,4747],{"class":78,"line":125},[76,4748,4749],{}," android:layout_height=\"30dp\"\n",[76,4751,4752],{"class":78,"line":130},[76,4753,4754],{}," android:gravity=\"center_vertical\"\n",[76,4756,4757],{"class":78,"line":136},[76,4758,4759],{}," android:padding=\"5dp\"/>\n",[76,4761,4762],{"class":78,"line":142},[76,4763,4764],{},"\u003C/LinearLayout>\n",[41,4766,4767],{},"Then we add the created adapter to a ListView (or a ListActivity like here):",[67,4769,4771],{"className":226,"code":4770,"language":228,"meta":11,"style":11},"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",[73,4772,4773,4778,4782,4787,4792,4797,4802,4807,4811,4816,4820,4825],{"__ignoreMap":11},[76,4774,4775],{"class":78,"line":79},[76,4776,4777],{},"public class ExpandableListActivity extends ListActivity {\n",[76,4779,4780],{"class":78,"line":12},[76,4781,3235],{},[76,4783,4784],{"class":78,"line":90},[76,4785,4786],{},"public void onCreate(Bundle savedInstanceState) {\n",[76,4788,4789],{"class":78,"line":96},[76,4790,4791],{}," super.onCreate(savedInstanceState);\n",[76,4793,4794],{"class":78,"line":102},[76,4795,4796],{}," List\u003CString> entries = new ArrayList\u003CString>();\n",[76,4798,4799],{"class":78,"line":108},[76,4800,4801],{}," for (int i = 1; i \u003C= 101; i++) {\n",[76,4803,4804],{"class":78,"line":114},[76,4805,4806],{}," entries.add(\"Entry \" + i);\n",[76,4808,4809],{"class":78,"line":120},[76,4810,3727],{},[76,4812,4813],{"class":78,"line":125},[76,4814,4815],{}," setListAdapter(new ExpandableListAdapter(entries, this));\n",[76,4817,4818],{"class":78,"line":130},[76,4819,287],{},[76,4821,4822],{"class":78,"line":136},[76,4823,4824],{}," private class ExpandableListAdapter extends BaseAdapter{....}\n",[76,4826,4827],{"class":78,"line":142},[76,4828,287],{},[41,4830,4831],{},"The list as it is now shows all entries, so the next thing to do, is implementing the limiting function to the adapter.",[41,4833,4834],{},"Let’s start by giving our adapter a member variable for the limit:",[67,4836,4838],{"className":226,"code":4837,"language":228,"meta":11,"style":11},"private int limit = 20;\n",[73,4839,4840],{"__ignoreMap":11},[76,4841,4842],{"class":78,"line":79},[76,4843,4837],{},[41,4845,4846],{},"Next we need to modify some of the methods to get this working:",[41,4848,4849],{},"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.",[67,4851,4853],{"className":226,"code":4852,"language":228,"meta":11,"style":11},"@Override\npublic int getCount() {\n if (entries.size() \u003C= limit) {\n return entries.size();\n }\n return limit;\n}\n",[73,4854,4855,4859,4864,4869,4874,4878,4883],{"__ignoreMap":11},[76,4856,4857],{"class":78,"line":79},[76,4858,3235],{},[76,4860,4861],{"class":78,"line":12},[76,4862,4863],{},"public int getCount() {\n",[76,4865,4866],{"class":78,"line":90},[76,4867,4868],{}," if (entries.size() \u003C= limit) {\n",[76,4870,4871],{"class":78,"line":96},[76,4872,4873],{}," return entries.size();\n",[76,4875,4876],{"class":78,"line":102},[76,4877,3727],{},[76,4879,4880],{"class":78,"line":108},[76,4881,4882],{}," return limit;\n",[76,4884,4885],{"class":78,"line":114},[76,4886,287],{},[41,4888,4889],{},"Next, we need to adjust the getItem method with an further clause in the if",[67,4891,4893],{"className":226,"code":4892,"language":228,"meta":11,"style":11},"if (position >= 0 && position \u003C limit && position \u003C entries.size())\n return entries.get(position);\n",[73,4894,4895,4900],{"__ignoreMap":11},[76,4896,4897],{"class":78,"line":79},[76,4898,4899],{},"if (position >= 0 && position \u003C limit && position \u003C entries.size())\n",[76,4901,4902],{"class":78,"line":12},[76,4903,4904],{}," return entries.get(position);\n",[41,4906,4907],{},"Now for the biggest change for our new functionality: the implementation of the ‘more button’.",[67,4909,4911],{"className":226,"code":4910,"language":228,"meta":11,"style":11},"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",[73,4912,4913,4918,4923,4928,4933,4938,4942,4947,4952],{"__ignoreMap":11},[76,4914,4915],{"class":78,"line":79},[76,4916,4917],{},"private LinearLayout getMoreView() {\n",[76,4919,4920],{"class":78,"line":12},[76,4921,4922],{}," LinearLayout moreView = (LinearLayout) getLayoutInflater().inflate(R.layout.more_row, null);\n",[76,4924,4925],{"class":78,"line":90},[76,4926,4927],{}," moreView.setOnClickListener(new View.OnClickListener() {\n",[76,4929,4930],{"class":78,"line":96},[76,4931,4932],{}," public void onClick(View v) {\n",[76,4934,4935],{"class":78,"line":102},[76,4936,4937],{}," listAdapter.increaseLimit();\n",[76,4939,4940],{"class":78,"line":108},[76,4941,105],{},[76,4943,4944],{"class":78,"line":114},[76,4945,4946],{}," });\n",[76,4948,4949],{"class":78,"line":120},[76,4950,4951],{}," return moreView;\n",[76,4953,4954],{"class":78,"line":125},[76,4955,287],{},[41,4957,4958],{},"Create another simple xml like this (disregarded i18n for this simple test case. Of course, Strings should normally be\ndeclared in the strings.xml):",[67,4960,4962],{"className":1124,"code":4961,"language":1126,"meta":11,"style":11},"\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",[73,4963,4964,4968,4972,4977,4982,4987,4992,4997,5001,5006,5011,5016,5021,5026],{"__ignoreMap":11},[76,4965,4966],{"class":78,"line":79},[76,4967,4709],{},[76,4969,4970],{"class":78,"line":12},[76,4971,4714],{},[76,4973,4974],{"class":78,"line":90},[76,4975,4976],{}," android:layout_width=\"fill_parent\"\n",[76,4978,4979],{"class":78,"line":96},[76,4980,4981],{}," android:layout_height=\"wrap_content\"\n",[76,4983,4984],{"class":78,"line":102},[76,4985,4986],{}," android:orientation=\"horizontal\"\n",[76,4988,4989],{"class":78,"line":108},[76,4990,4991],{}," android:paddingLeft=\"20dp\"\n",[76,4993,4994],{"class":78,"line":114},[76,4995,4996],{}," android:paddingRight=\"20dp\">\n",[76,4998,4999],{"class":78,"line":120},[76,5000,4734],{},[76,5002,5003],{"class":78,"line":125},[76,5004,5005],{}," android:id=\"@+id/MoreRowText\"\n",[76,5007,5008],{"class":78,"line":130},[76,5009,5010],{}," android:layout_width=\"fill_parent\"\n",[76,5012,5013],{"class":78,"line":136},[76,5014,5015],{}," android:layout_height=\"40dp\"\n",[76,5017,5018],{"class":78,"line":142},[76,5019,5020],{}," android:text=\"Load more...\"\n",[76,5022,5023],{"class":78,"line":147},[76,5024,5025],{}," android:gravity=\"center\"/>\n",[76,5027,5028],{"class":78,"line":152},[76,5029,4764],{},[41,5031,5032],{},"And add a method to the adapter to increase the limit:",[67,5034,5036],{"className":226,"code":5035,"language":228,"meta":11,"style":11}," 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",[73,5037,5038,5043,5048,5053,5058,5063,5067,5072,5077],{"__ignoreMap":11},[76,5039,5040],{"class":78,"line":79},[76,5041,5042],{}," public void increaseLimit() {\n",[76,5044,5045],{"class":78,"line":12},[76,5046,5047],{}," limit+=20;\n",[76,5049,5050],{"class":78,"line":90},[76,5051,5052],{}," //remove the button if we can no longer expand the list\n",[76,5054,5055],{"class":78,"line":96},[76,5056,5057],{}," if (limit >= entries.size()) {\n",[76,5059,5060],{"class":78,"line":102},[76,5061,5062],{}," getListView().removeFooterView(moreView);\n",[76,5064,5065],{"class":78,"line":108},[76,5066,3727],{},[76,5068,5069],{"class":78,"line":114},[76,5070,5071],{}," //notify to redraw the list\n",[76,5073,5074],{"class":78,"line":120},[76,5075,5076],{}," notifyDataSetChanged();\n",[76,5078,5079],{"class":78,"line":125},[76,5080,3727],{},[41,5082,5083],{},"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):",[67,5085,5087],{"className":226,"code":5086,"language":228,"meta":11,"style":11},"moreView = getMoreView();\nlistAdapter = new ExpandableListAdapter(entries);\ngetListView().addFooterView(moreView);\nsetListAdapter(listAdapter);\n",[73,5088,5089,5094,5099,5104],{"__ignoreMap":11},[76,5090,5091],{"class":78,"line":79},[76,5092,5093],{},"moreView = getMoreView();\n",[76,5095,5096],{"class":78,"line":12},[76,5097,5098],{},"listAdapter = new ExpandableListAdapter(entries);\n",[76,5100,5101],{"class":78,"line":90},[76,5102,5103],{},"getListView().addFooterView(moreView);\n",[76,5105,5106],{"class":78,"line":96},[76,5107,5108],{},"setListAdapter(listAdapter);\n",[41,5110,5111],{},"And that’s it for the simple case of a static list 🙂",[41,5113,5114],{},"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.",[5116,5117,5118,5121,5124],"ol",{},[344,5119,5120],{},"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",[344,5122,5123],{},"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)",[344,5125,5126],{},"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.",[41,5128,5129,5130,5136],{},"A Little homework for you: Combine it with\nthe ",[45,5131,5135],{"href":5132,"rel":5133,"title":5134},"http://blog.synyx.de/2011/11/android-listview-with-rounded-corners/",[49],"android listview with rounded corners","rounded corners example","\nto make it look better 😛",[41,5138,5139,5140],{},"Here’s the source, btw:",[45,5141,5144],{"href":5142,"rel":5143},"https://media.synyx.de/uploads//2012/12/ExpandableListview.zip",[49],"ExpandableListview",[295,5146,297],{},{"title":11,"searchDepth":12,"depth":12,"links":5148},[],[1791,1792],"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":4497,"description":4506},"blog/android-expandable-listview",[314,5158,5159,5160,5161,5162,5163,5164],"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":5168,"title":5169,"author":5170,"body":5171,"category":5272,"date":5274,"description":5275,"extension":16,"link":5276,"meta":5277,"navigation":23,"path":5278,"seo":5279,"slug":5175,"stem":5280,"tags":5281,"teaser":5285,"__hash__":5286},"blog/blog/meine-ausbildung-bei-synyx-teil-2.md","Meine Ausbildung bei synyx – Teil 2",[26],{"type":8,"value":5172,"toc":5267},[5173,5176,5187,5196,5200,5203,5207,5210,5217,5244,5248,5251,5257,5264],[37,5174,5169],{"id":5175},"meine-ausbildung-bei-synyx-teil-2",[41,5177,5178],{},[1159,5179,5180,5181,5186],{},"(Fortsetzung ",[45,5182,5185],{"href":5183,"rel":5184},"http://blog.synyx.de/2011/11/meine-ausbildung-bei-synyx/",[49],"meines Blogposts"," von letztem Jahr)",[41,5188,5189,5190,5195],{},"Das dritte Jahr meiner Ausbildung bei synyx spielte sich zum Großteil in der\nAbteilung ",[45,5191,5194],{"href":5192,"rel":5193},"https://synyx.de/code-clinic-softwareoptimierung/",[49],"Code Clinic"," ab, in der ich etliche neue Technologien und\nFrameworks kennengelernt und eingesetzt habe.",[621,5197,5199],{"id":5198},"rückblick","Rückblick",[41,5201,5202],{},"Bei meinen bisherigen Projekten war es so, dass man den Code zu einem guten Teil kannte, weil man an vielen Stellen\ndavon selbst mitentwickelt hat. Die Technologien waren auch oftmals bekannt, und wenn nicht, hatte man sich kurz bei\nKollegen erkundigt, was für das Problem passt, oder aus eigener Erfahrung ein passendes Framework ausgesucht und es\neingesetzt. Die Anwendungen liefen meist auf nur einem Server, auf dem auch die Datenbank war, oder mit externem\nDatenbankserver, was aber kaum Mehraufwand beim Planen und Umsetzen bedeutet. Fachwissen benötigte man nur wenig für die\nUmsetzung der Anwendungen und was man davon benötigte, das hatte man schnell abgeklärt.",[621,5204,5206],{"id":5205},"willkommen-in-der-enterprise-welt","Willkommen in der Enterprise Welt!",[41,5208,5209],{},"Nicht so in der Code Clinic.",[41,5211,5212,5216],{},[213,5213],{"alt":5214,"src":5215},"\"code_clinic\"","https://media.synyx.de/uploads//2012/07/code_clinic.jpg","\nHier galt es sich erst einmal in ein sehr großes Projekt mit einiger alten Technik einzuarbeiten, um darin Änderungen\nvorzunehmen. Danach teilweise aber auch größere Teile dieses Projektes Stück für Stück mit neuer Technologie und\nbesserem Code zu ersetzen, damit diese auf die Dauer besser wartbar wird und zuverlässiger und performanter arbeitet.\nAlles auf einmal zu ersetzen war hierbei keine Option, da dies viel zu lange dauern würde und einige Stellen dringender\nwaren, als andere. Dabei war ein sehr ins Gewicht fallender Unterschied zu allen bisherigen Projekten der große Anteil\nder Analyse und Planung am Gesamtaufwand. Man muss sich zuerst einmal in den alten Code einarbeiten, verstehen, was\ndarin gemacht wird, Analysieren, von wo dieser Code in welcher Form verwendet wird, usw. bevor man damit anfängt, sich\nzu überlegen, wie man das besser machen kann, welche Technologien man dafür einsetzt und auch sicherstellt, dass dies\nauch im eingesetzten Projektumfeld funktioniert. Auch wird einiges an fachlichem Wissen für das Entwickeln benötigt. Man\nkann nicht einfach alles aus dem alten Code herauslesen, sonst implementiert man es nur nach, und nicht neu. Daher muss\nman auch viel mit Kunden kommunizieren, in Erfahrung bringen, wie genau etwas funktionierten soll, was für eine Aufgabe\nes erfüllen muss und mit welchen Ausnahmefällen eventuell zu rechnen ist. Hinzu kommt, dass die Anwendung auf mehreren\nStandorten läuft. In unterschiedlichen Versionen und jeweils mit besonderen Anpassungen. Auch hier liegt es an uns, dies\nzu verbessern.",[41,5218,5219,5220,5226,5227,5232,5233,5239,5240,5243],{},"Alles in Allem ist die Arbeit hier oftmals stressiger und anstrengender als vorher, aber ich konnte viel lernen (vor\nallem den Umgang mit Kunden) und einige neue Technologien einsetzen. So wird zum Beispiel eine Oracle Datenbank genutzt,\ndie doch schon ein wenig anders funktioniert, als die bisher gewohnten MySQL oder PostgreSQL Datenbanken. Auf der Oracle\nDatanbank durfte ich zum Beispiel mit RMI und JMS experimentieren, damit man die Businesslogik, die bisher teilweise\ndirekt in der Datenbank mit Triggern und Prozeduren realisiert war, langsam im Java Code auslagern konnte. Außerdem\nwollen wir die bisher große Anwendung bei der Neuimplementierung in mehrere kleinere Anwendungen aufgeteilt, die auf\nmehreren Servern verteilt laufen und damit besser skalierbar sind. Diese können zum Beispiel über eine JMS queues\nmiteinander kommunizieren und sich über RMI Java Klassen und Services zur Verfügung stellen. Dann war ich noch bei einer\nEvalualisierung von Reporting- und Dokumenterzeugungs- engines beteiligt, da die bisherige ersetzt werden sollte.\nMögliche Kandidaten waren\nhier ",[45,5221,5225],{"href":5222,"rel":5223,"title":5224},"http://www.eclipse.org/birt/phoenix/",[49],"birt","Birt",", ",[45,5228,5231],{"href":5229,"rel":5230,"title":5231},"http://jasperforge.org/projects/jasperreports/",[49],"JasperReports","\nund Crystal Reports, wobei wir uns am Ende für Birt entschieden haben. Außerden haben wir für die neue Oberfläche\nauf ",[45,5234,5238],{"href":5235,"rel":5236,"title":5237},"http://wicket.apache.org/",[49],"wicket","Wicket","gesetzt, das einem, wenn man sich erst einmal darin eingearbeitet hat,\neine Menge Arbeit mit Javascript/Ajax abnimmt. Da die Anwendung auch an unterschiedlichen Standorten läuft und man das\nDatenbankschema auf dem richtigen Stand haben will, wenn man die Anwendung deployed, setzten wir\nhierfür ",[45,5241,2555],{"href":2553,"rel":5242,"title":3189},[49]," ein, das diese Arbeit für uns erleichtert und die Änderungen\nautomatisiert vornehmen kann.",[621,5245,5247],{"id":5246},"abschlussprojekt","Abschlussprojekt",[41,5249,5250],{},"Ab und an war ich jedoch auch in anderen Projekten, wie zum Beispiel meinem Abschlussprojekt.",[41,5252,5253],{},[213,5254],{"alt":11,"src":5255,"title":5256},"https://media.synyx.de/uploads//2012/07/detail_-150x150.jpg","detail_",[41,5258,5259,5263],{},[213,5260],{"alt":5261,"src":5262},"\"list_snip_\"","https://media.synyx.de/uploads//2012/07/list_snip_.jpg","\nZeitlich hat das Projekt sehr gut geklappt und vor der Präsentation bei der IHK konnte ich 2 mal in der Firma\npräsentieren und bekam Feedback, was ich denn noch besser machen könnte. So lief dann auch die Präsentation vor den\nPrüfern super. Die Prüfer waren jedoch recht überrascht, dass man mittlerweile auch mit dem Smartphone präsentieren\nkann (HDMI-Ausgang ftw! 😀 ) und die Katzen auf den Präsentationsfolien waren auch ein Erfolg. Allein die Dokumentation\nwurde nicht sooo gut bewertet, die Gründe dafür sind mir jedoch noch nicht bekannt. Erstmal einen Termin zur Einsicht\nvereinbaren, usw… Bürokratie >.>",[41,5265,5266],{},"Bestanden habe ich aber natürlich und bin seitdem als Junior Developer bei synyx angestellt!",{"title":11,"searchDepth":12,"depth":12,"links":5268},[5269,5270,5271],{"id":5198,"depth":90,"text":5199},{"id":5205,"depth":90,"text":5206},{"id":5246,"depth":90,"text":5247},[5273],"azubi-blog","2012-08-21T10:05:46","(Fortsetzung meines Blogposts von letztem Jahr)","https://synyx.de/blog/meine-ausbildung-bei-synyx-teil-2/",{},"/blog/meine-ausbildung-bei-synyx-teil-2",{"title":5169,"description":5275},"blog/meine-ausbildung-bei-synyx-teil-2",[5282,5283,5284],"ausbildung","azubi-2","fachinformatiker-anwendungsentwicklung","(Fortsetzung meines Blogposts von letztem Jahr) Das dritte Jahr meiner Ausbildung bei synyx spielte sich zum Großteil in der Abteilung Code Clinic ab, in der ich etliche neue Technologien und…","APvLrTGR6DcYaFkpKKdGvyQx5pgfZci7sRMIeJ1fp88",{"id":5288,"title":5289,"author":5290,"body":5291,"category":5506,"date":5507,"description":5508,"extension":16,"link":5509,"meta":5510,"navigation":23,"path":5511,"seo":5512,"slug":5513,"stem":5514,"tags":5515,"teaser":5519,"__hash__":5520},"blog/blog/android-2-1-sqlite-problem-with-querybuilder-and-distinct.md","Android 2.1 SQLite: problem with QueryBuilder and Distinct",[26],{"type":8,"value":5292,"toc":5504},[5293,5296,5299,5302,5480,5487,5490,5499,5502],[37,5294,5289],{"id":5295},"android-21-sqlite-problem-with-querybuilder-and-distinct",[41,5297,5298],{},"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.",[41,5300,5301],{},"Here’s the simplified code:",[67,5303,5305],{"className":226,"code":5304,"language":228,"meta":11,"style":11},"//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",[73,5306,5307,5312,5317,5322,5327,5332,5337,5342,5347,5352,5357,5362,5367,5372,5377,5382,5387,5392,5397,5402,5407,5412,5417,5422,5427,5432,5437,5442,5447,5452,5457,5462,5466,5471,5476],{"__ignoreMap":11},[76,5308,5309],{"class":78,"line":79},[76,5310,5311],{},"//member, a SQLiteOpenHelper\n",[76,5313,5314],{"class":78,"line":12},[76,5315,5316],{},"BackendOpenHelper helper;\n",[76,5318,5319],{"class":78,"line":90},[76,5320,5321],{},"//...\n",[76,5323,5324],{"class":78,"line":96},[76,5325,5326],{},"public List \u003CExample> getExamples(String arg){\n",[76,5328,5329],{"class":78,"line":102},[76,5330,5331],{},"SQLiteQueryBuilder builder = new SQLiteQueryBuilder();\n",[76,5333,5334],{"class":78,"line":108},[76,5335,5336],{}," builder.setTables(\"example e JOIN\n",[76,5338,5339],{"class":78,"line":114},[76,5340,5341],{}," secondtable s ON e.id = s.example_id\");\n",[76,5343,5344],{"class":78,"line":120},[76,5345,5346],{}," Map\u003CString, String> projectionMap =\n",[76,5348,5349],{"class":78,"line":125},[76,5350,5351],{}," new HashMap\u003CString, String>();\n",[76,5353,5354],{"class":78,"line":130},[76,5355,5356],{}," projectionMap.put(\"id\", \"e.id\");\n",[76,5358,5359],{"class":78,"line":136},[76,5360,5361],{}," //... put in some more values ...\n",[76,5363,5364],{"class":78,"line":142},[76,5365,5366],{}," builder.setProjectionMap(projectionMap);\n",[76,5368,5369],{"class":78,"line":147},[76,5370,5371],{}," builder.setDistinct(true);\n",[76,5373,5374],{"class":78,"line":152},[76,5375,5376],{}," builder.appendWhere(\" e.someRow = ? \");\n",[76,5378,5379],{"class":78,"line":158},[76,5380,5381],{}," //... some more wheres ...\n",[76,5383,5384],{"class":78,"line":164},[76,5385,5386],{}," SQLiteDatabase db = helper.getReadableDatabase();\n",[76,5388,5389],{"class":78,"line":169},[76,5390,5391],{}," String[] selectionArgs = new String[] {\n",[76,5393,5394],{"class":78,"line":174},[76,5395,5396],{}," arg\n",[76,5398,5399],{"class":78,"line":180},[76,5400,5401],{}," };\n",[76,5403,5404],{"class":78,"line":186},[76,5405,5406],{}," Cursor cursor = builder.query(db, null,\n",[76,5408,5409],{"class":78,"line":191},[76,5410,5411],{}," null, selectionArgs, null, null, null);\n",[76,5413,5414],{"class":78,"line":196},[76,5415,5416],{}," if (cursor.moveToFirst()) {\n",[76,5418,5419],{"class":78,"line":558},[76,5420,5421],{}," while (cursor.isAfterLast() == false) {\n",[76,5423,5424],{"class":78,"line":563},[76,5425,5426],{}," int index = cursor.getColumnIndex(\"id\");\n",[76,5428,5429],{"class":78,"line":568},[76,5430,5431],{}," //on android 2.1, index is returned as -1\n",[76,5433,5434],{"class":78,"line":573},[76,5435,5436],{}," //on newer versions as 1\n",[76,5438,5439],{"class":78,"line":579},[76,5440,5441],{}," int id = cursor.getInt(index);\n",[76,5443,5444],{"class":78,"line":585},[76,5445,5446],{}," //crashes if index is -1\n",[76,5448,5449],{"class":78,"line":590},[76,5450,5451],{}," //...\n",[76,5453,5454],{"class":78,"line":1620},[76,5455,5456],{}," cursor.moveToNext();\n",[76,5458,5459],{"class":78,"line":1625},[76,5460,5461],{}," }\n",[76,5463,5464],{"class":78,"line":1630},[76,5465,105],{},[76,5467,5468],{"class":78,"line":1636},[76,5469,5470],{}," cursor.close();\n",[76,5472,5473],{"class":78,"line":1641},[76,5474,5475],{}," //...\n",[76,5477,5478],{"class":78,"line":1647},[76,5479,287],{},[41,5481,5482,5483,5486],{},"After some research I found out that this apparently happens, when using ",[1159,5484,5485],{},"distinct"," with the QueryBuilder on android\n2.1.",[41,5488,5489],{},"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).",[67,5491,5493],{"className":226,"code":5492,"language":228,"meta":11,"style":11}," int id = cursor.getInt(1);\n",[73,5494,5495],{"__ignoreMap":11},[76,5496,5497],{"class":78,"line":79},[76,5498,5492],{},[41,5500,5501],{},"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.",[295,5503,297],{},{"title":11,"searchDepth":12,"depth":12,"links":5505},[],[1791,1792],"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":5289,"description":5298},"android-2-1-sqlite-problem-with-querybuilder-and-distinct","blog/android-2-1-sqlite-problem-with-querybuilder-and-distinct",[5516,314,3186,5517,5518],"2-1","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":5522,"title":5523,"author":5524,"body":5525,"category":5841,"date":5842,"description":5843,"extension":16,"link":5844,"meta":5845,"navigation":23,"path":5846,"seo":5847,"slug":5529,"stem":5848,"tags":5849,"teaser":5851,"__hash__":5852},"blog/blog/android-listview-with-rounded-corners.md","Android: ListView with rounded corners",[26],{"type":8,"value":5526,"toc":5839},[5527,5530,5533,5536,5539,5542,5609,5612,5698,5701,5704,5747,5750,5753,5834,5837],[37,5528,5523],{"id":5529},"android-listview-with-rounded-corners",[41,5531,5532],{},"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.",[41,5534,5535],{},"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:",[41,5537,5538],{},"First off, we need the drawables for the backgrounds of the Lists entries:",[41,5540,5541],{},"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:",[67,5543,5545],{"className":1124,"code":5544,"language":1126,"meta":11,"style":11},"\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",[73,5546,5547,5552,5557,5562,5567,5572,5577,5582,5587,5591,5596,5600,5604],{"__ignoreMap":11},[76,5548,5549],{"class":78,"line":79},[76,5550,5551],{},"\u003C?xml version=\"1.0\" encoding=\"UTF-8\"?>\n",[76,5553,5554],{"class":78,"line":12},[76,5555,5556],{},"\u003Clayer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n",[76,5558,5559],{"class":78,"line":90},[76,5560,5561],{}," \u003Citem>\n",[76,5563,5564],{"class":78,"line":96},[76,5565,5566],{}," \u003Cshape>\n",[76,5568,5569],{"class":78,"line":102},[76,5570,5571],{}," \u003Cstroke android:width=\"1px\" android:color=\"#ffbbbbbb\"/>\n",[76,5573,5574],{"class":78,"line":108},[76,5575,5576],{}," \u003C/shape>\n",[76,5578,5579],{"class":78,"line":114},[76,5580,5581],{}," \u003C/item>\n",[76,5583,5584],{"class":78,"line":120},[76,5585,5586],{}," \u003Citem android:bottom=\"1dp\" android:left=\"1dp\" android:right=\"1dp\">\n",[76,5588,5589],{"class":78,"line":125},[76,5590,5566],{},[76,5592,5593],{"class":78,"line":130},[76,5594,5595],{}," \u003Csolid android:color=\"#ffffffff\"/>\n",[76,5597,5598],{"class":78,"line":136},[76,5599,5576],{},[76,5601,5602],{"class":78,"line":142},[76,5603,5581],{},[76,5605,5606],{"class":78,"line":147},[76,5607,5608],{},"\u003C/layer-list>\n",[41,5610,5611],{},"For the rounded corners, create another xml, “rounded_corner_top.xml”:",[67,5613,5615],{"className":1124,"code":5614,"language":1126,"meta":11,"style":11},"\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",[73,5616,5617,5621,5625,5629,5633,5638,5643,5648,5653,5657,5661,5666,5670,5674,5678,5682,5686,5690,5694],{"__ignoreMap":11},[76,5618,5619],{"class":78,"line":79},[76,5620,4709],{},[76,5622,5623],{"class":78,"line":12},[76,5624,5556],{},[76,5626,5627],{"class":78,"line":90},[76,5628,5561],{},[76,5630,5631],{"class":78,"line":96},[76,5632,5566],{},[76,5634,5635],{"class":78,"line":102},[76,5636,5637],{}," \u003Cstroke android:width=\"1dp\" android:color=\"#ffbbbbbb\"/>\n",[76,5639,5640],{"class":78,"line":108},[76,5641,5642],{}," \u003Ccorners android:topLeftRadius=\"20dp\"\n",[76,5644,5645],{"class":78,"line":114},[76,5646,5647],{}," android:topRightRadius=\"20dp\"\n",[76,5649,5650],{"class":78,"line":120},[76,5651,5652],{}," />\n",[76,5654,5655],{"class":78,"line":125},[76,5656,5576],{},[76,5658,5659],{"class":78,"line":130},[76,5660,5581],{},[76,5662,5663],{"class":78,"line":136},[76,5664,5665],{}," \u003Citem android:top=\"1dp\" android:left=\"1dp\" android:right=\"1dp\" android:bottom=\"1dp\">\n",[76,5667,5668],{"class":78,"line":142},[76,5669,5566],{},[76,5671,5672],{"class":78,"line":147},[76,5673,5595],{},[76,5675,5676],{"class":78,"line":152},[76,5677,5642],{},[76,5679,5680],{"class":78,"line":158},[76,5681,5647],{},[76,5683,5684],{"class":78,"line":164},[76,5685,5652],{},[76,5687,5688],{"class":78,"line":169},[76,5689,5576],{},[76,5691,5692],{"class":78,"line":174},[76,5693,5581],{},[76,5695,5696],{"class":78,"line":180},[76,5697,5608],{},[41,5699,5700],{},"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)",[41,5702,5703],{},"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:",[67,5705,5707],{"className":1124,"code":5706,"language":1126,"meta":11,"style":11},"\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",[73,5708,5709,5713,5718,5723,5728,5732,5737,5742],{"__ignoreMap":11},[76,5710,5711],{"class":78,"line":79},[76,5712,368],{"emptyLinePlaceholder":23},[76,5714,5715],{"class":78,"line":12},[76,5716,5717],{},"\u003Cselector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n",[76,5719,5720],{"class":78,"line":90},[76,5721,5722],{}," \u003Citem android:drawable=\"@drawable/rounded_corner_top_click\"\n",[76,5724,5725],{"class":78,"line":96},[76,5726,5727],{}," android:state_pressed=\"true\"/>\n",[76,5729,5730],{"class":78,"line":102},[76,5731,5722],{},[76,5733,5734],{"class":78,"line":108},[76,5735,5736],{}," android:state_focused=\"true\"/>\n",[76,5738,5739],{"class":78,"line":114},[76,5740,5741],{}," \u003Citem android:drawable=\"@drawable/rounded_corner_top\"/>\n",[76,5743,5744],{"class":78,"line":120},[76,5745,5746],{},"\u003C/selector>\n",[41,5748,5749],{},"Now do the same for the other backgrounds of the list.",[41,5751,5752],{},"All that is left now, is to assign the right backgrounds in our ListAdapter like following:",[67,5754,5756],{"className":226,"code":5755,"language":228,"meta":11,"style":11},"@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",[73,5757,5758,5762,5767,5772,5777,5782,5787,5792,5797,5802,5807,5812,5817,5821,5825,5830],{"__ignoreMap":11},[76,5759,5760],{"class":78,"line":79},[76,5761,3235],{},[76,5763,5764],{"class":78,"line":12},[76,5765,5766],{},"public View getView(int position, View convertView, ViewGroup parent) {\n",[76,5768,5769],{"class":78,"line":90},[76,5770,5771],{}," //...\n",[76,5773,5774],{"class":78,"line":96},[76,5775,5776],{}," //skipping the view reuse stuff\n",[76,5778,5779],{"class":78,"line":102},[76,5780,5781],{}," if (position == 0 && entry_list.size() == 1) {\n",[76,5783,5784],{"class":78,"line":108},[76,5785,5786],{}," view.setBackgroundResource(R.drawable.selector_rounded_corner);\n",[76,5788,5789],{"class":78,"line":114},[76,5790,5791],{}," } else if (position == 0) {\n",[76,5793,5794],{"class":78,"line":120},[76,5795,5796],{}," view.setBackgroundResource(R.drawable.selector_rounded_corner_top);\n",[76,5798,5799],{"class":78,"line":125},[76,5800,5801],{}," } else if (position == entry_list.size() - 1) {\n",[76,5803,5804],{"class":78,"line":130},[76,5805,5806],{}," view.setBackgroundResource(R.drawable.selector_rounded_corner_bottom);\n",[76,5808,5809],{"class":78,"line":136},[76,5810,5811],{}," } else {\n",[76,5813,5814],{"class":78,"line":142},[76,5815,5816],{}," view.setBackgroundResource(R.drawable.selector_middle);\n",[76,5818,5819],{"class":78,"line":147},[76,5820,199],{},[76,5822,5823],{"class":78,"line":152},[76,5824,5771],{},[76,5826,5827],{"class":78,"line":158},[76,5828,5829],{}," //skipping the filling of the view\n",[76,5831,5832],{"class":78,"line":164},[76,5833,287],{},[41,5835,5836],{},"Aaaand we’re done.",[295,5838,297],{},{"title":11,"searchDepth":12,"depth":12,"links":5840},[],[1791,1792],"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":5523,"description":5532},"blog/android-listview-with-rounded-corners",[314,5163,5850,5164],"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":5854,"title":5855,"author":5856,"body":5857,"category":5970,"date":5971,"description":11,"extension":16,"link":5972,"meta":5973,"navigation":23,"path":5974,"seo":5975,"slug":5976,"stem":5977,"tags":5978,"teaser":5979,"__hash__":5980},"blog/blog/meine-ausbildung-bei-synyx-2.md","Meine Ausbildung bei Synyx",[26],{"type":8,"value":5858,"toc":5965},[5859,5862,5868,5871,5874,5879,5883,5886,5889,5892,5898,5902,5905,5908,5911,5915,5921,5924,5933,5940,5943,5952,5959,5962],[37,5860,5855],{"id":5861},"meine-ausbildung-bei-synyx",[41,5863,5864],{},[213,5865],{"alt":5866,"src":5867},"\"Ausbildungswerkzeug 1\"","https://media.synyx.de/uploads//2011/11/IMG_20111109_115806.jpg",[41,5869,5870],{},"Ausbildungswerkzeug 1",[41,5872,5873],{},"Nach etwas mehr als 2 Jahren nehme ich mir mal die Zeit, etwas über meine Ausbildung bei Synyx zu schreiben (Und das\nnicht nur, weil es sonst mit dem Paddel gibt ",[41,5875,5876],{},[1159,5877,5878],{},"Vor der Ausbildung wusste ich noch nicht so recht, was mich erwarten wird. Ich hatte vorher zwar schon einmal ein\nmehrwöchiges Praktikum bei einer anderen Firma absolviert, aber darin hatte ich nicht wirklich viel vom Beruf des\nFachinformatikers mitbekommen. Programmieren hatte ich vorher schon ein bisschen in der Schule und da mir das gefiel,\nentschied ich mich für diesen Beruf. Außerdem tat ich mich zu der Zeit noch recht schwer mit neuen Leuten konfrontiert\nzu werden, auch wenn ich die meisten schon auf der vorherigen Sommerfeier kennen lernen konnte.",[56,5880,5882],{"id":5881},"erstes-jahr","Erstes Jahr",[41,5884,5885],{},"Im September 2009 begann meine Ausbildung bei Synyx damit, meinen Rechner einzurichten. Dafür hatte mir mein Ausbilder\nAlex einen Zettel mit ein paar Anweisungen und Tipps + eine IP-Adresse gegeben, von der aus ich Linux per netinstall\ninstallieren konnte.",[41,5887,5888],{},"In den folgenden Wochen und Monaten bekam ich von Alex die Grundlagen für die Entwicklung mit Java und auch mit JSP,\nHTML und CSS gezeigt. Die Zeit, in der er mit anderen Sachen beschäftigt war, konnte ich damit verbringen, Bücher zu\nlesen und kleine Übungen und Testprojekte anzugehen, um mehr Übung mit dem Programmieren zu bekommen. Wenn ich mit etwas\nnicht weiterkam, konnte ich stets Alex oder andere Mitarbeiter fragen und bekam von ihnen alles super erklärt.",[41,5890,5891],{},"Die Berufsschule war von Anfang an überhaupt kein Problem, da ich das Meiste vorher schon einmal in der Schule, oder,\nwie beim Programmieren, bei Synyx gelernt hatte.",[41,5893,5894],{},[213,5895],{"alt":11,"src":5896,"title":5897},"https://media.synyx.de/uploads//2011/11/sudoku_screenshot2-200x300.png","sudoku_screenshot2",[56,5899,5901],{"id":5900},"zweites-jahr","Zweites Jahr",[41,5903,5904],{},"In meinem zweiten Jahr wurde ich einem Produktivprojekt, das auf OpenCMS aufbaut, zugeteilt und habe so einerseits den\nUmgang mit OpenCMS, viel wichtiger jedoch, das Arbeiten im Team gelernt. Auch hier wurde mir jederzeit unter die Arme\ngegriffen, wenn ich Hilfe benötigte und so fand ich mich gut im Projekt ein. In diesem Projekt war ich auch erstmals bei\nMeetings mit Kunden dabei, was mir anfangs noch ziemlich unangenehm war. Nach ein paar Meetings, und als ich mehr Ahnung\nvom Projekt hatte, war das aber auch kein Problem mehr.",[41,5906,5907],{},"Teilweise arbeitete ich nebenbei an I-think-I-spider und anderen kleinen Sachen weiter. Zeit zum Lernen wurde mir\naber auch weiterhin noch gegeben.",[41,5909,5910],{},"Im Laufe des Jahres kam noch ein weiteres OpenCMS Projekt hinzu, das aber etwas anders aufgebaut war und somit doch\nwieder reichlich neue Erfahrungen brachte. Das Projekt wurde vorher von einer anderen Firma geleitet und nun sollten wir\ndaran weiter programmieren. Interessant war dabei, zu sehen, wie unterschiedlich die Ansätze der anderen Firma für\nverschiedene Sachen waren, obwohl die vorhandene Technik eigentlich dieselbe war.",[56,5912,5914],{"id":5913},"heute","Heute",[41,5916,5917],{},[213,5918],{"alt":5919,"src":5920},"\"Ausbildungswerkzeug 2\"","https://media.synyx.de/uploads//2011/11/IMG_20111109_120228.jpg",[41,5922,5923],{},"Ausbildungswerkzeug 2",[41,5925,5926,5927,5932],{},"Nun stehe ich am Anfang meines dritten Ausbildungsjahres und wurde auch hier wieder einer neuen Abteilung bei uns,\nder ",[45,5928,5194],{"href":5929,"rel":5930,"title":5931},"http://www.synyx.de/de/leistungen/code-clinic.html",[49],"Synyx Code Clinic"," zugeteilt. Hier ist das\nProgramm, alte, gegen die Wand gefahrene Projekte von anderen Firmen zu retten und zu verarzten. Somit lerne ich wieder\neine ganz andere Seite des Berufes kennen: Eine ganz andere Projektstruktur, als ich sie bisher gesehen hatte, nur sehr\nwenig sauberer und verständlicher Code, alles komplett veraltet und viel Politik. Einerseits ist es anstrengender, als\ndie bisherigen Projekte, andererseits lerne ich aber auch sehr viel dabei.",[41,5934,5935],{},[903,5936,5937],{},[1159,5938,5939],{},"-Ein paar Wochen später-",[41,5941,5942],{},"Wie bereits erwartet, ist es etwas ganz anderes, sich in ein so großes Projekt einzuarbeiten, als es bei meinen\nbisherigen Projekten der Fall war. Vor allem, weil viele Techniken eingesetzt werden, die ich bisher noch nicht\nverwendet hatte, aber auch, weil es von anderern programmiert wurde und der Code einfach einen ganz anderen Stil (und\nQualität…) hat, als ich es von Synyx gewohnt bin.",[41,5944,5945,5946,5951],{},"Die Problematik des Einarbeitens in ein fremdes Projekt wurde mir nochmals bewusst, als ich ein Android Projekt für das\nZKM in Karlsruhe übernahm –\ndie ",[45,5947,5950],{"href":5948,"rel":5949,"title":5950},"http://www.karlsruhe.de/b1/kultur/kulturinkarlsruhe/kulturapp.de",[49],"KulturApp Karlsruhe",". Hier\nwar es weniger die Größe, sondern vielmehr alle anderen oben genannten Punkte. Schmerzhaft musste ich in diesem Projekt\nfeststellen, wie man sich bei einem solchen Vorhaben dann auch mal mit dem Zeitaufwand verschätzen kann, wenn man diese\nFaktoren nicht genügend berücksichtigt. Davon schreibe ich jedoch später eventuell mehr in einem anderen Blogeintrag. Um\neinige Erfahrungen reicher bin ich durch dieses Projekt aber auf jeden Fall geworden und werde mich in zukünftigen\nProjekten besser vor den genannten Problemen hüten.",[41,5953,5954],{},[903,5955,5956],{},[1159,5957,5958],{},"So…",[41,5960,5961],{},"das war es vorerst einmal von meiner Ausbildung, die manchmal ein wenig chaotisch abläuft, die aber (oder vielleicht\ngerade deshalb) doch sehr viel Spaß macht. Synyx ist ein lustiger Haufen, man lernt hier sehr viel und bekommt jederzeit\ngeholfen. Da hatte ich echt Glück, dass mir die erste Ausbildungsstelle, die ich fast bekommen hätte, im letzten\nAugenblick doch abgesagt wurde.",[41,5963,5964],{},"Ich hoffe ich habe euch hiermit einen guten Einblick verschafft und dass ich vielleicht den ein oder anderen dazu\nbewege, sich auch bei Synyx zu bewerben. 😛",{"title":11,"searchDepth":12,"depth":12,"links":5966},[5967,5968,5969],{"id":5881,"depth":12,"text":5882},{"id":5900,"depth":12,"text":5901},{"id":5913,"depth":12,"text":5914},[5273],"2011-11-09T12:28:33","https://synyx.de/blog/meine-ausbildung-bei-synyx-2/",{},"/blog/meine-ausbildung-bei-synyx-2",{"title":5855,"description":11},"meine-ausbildung-bei-synyx-2","blog/meine-ausbildung-bei-synyx-2",[5282],"Ausbildungswerkzeug 1 Nach etwas mehr als 2 Jahren nehme ich mir mal die Zeit, etwas über meine Ausbildung bei Synyx zu schreiben (Und das nicht nur, weil es sonst mit…","-LtTxuEL3_9850xlaYlPEg1DRIY473gTOmCAK9blNeg",{"id":5982,"title":5983,"author":5984,"body":5985,"category":6277,"date":6278,"description":6279,"extension":16,"link":6280,"meta":6281,"navigation":23,"path":6282,"seo":6283,"slug":5989,"stem":6284,"tags":6285,"teaser":6288,"__hash__":6289},"blog/blog/evaluating-mobile-multiplatform-frameworks.md","Evaluating Mobile Multiplatform Frameworks",[26],{"type":8,"value":5986,"toc":6275},[5987,5990,5993,6017,6020,6032,6035,6038,6041,6098,6107,6257,6260,6263,6266,6269,6272],[37,5988,5983],{"id":5989},"evaluating-mobile-multiplatform-frameworks",[41,5991,5992],{},"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.",[41,5994,5995,5999,6000,6005,6006,6011,6012],{},[213,5996],{"alt":5997,"src":5998},"\"phonegap_logo\"","https://media.synyx.de/uploads//2011/05/phonegap_symbian.jpg","\nFirst there is PhoneGap (",[45,6001,6004],{"href":6002,"rel":6003},"https://web.archive.org/web/20210302121558/https://phonegap.com/",[49],"http://www.phonegap.com/",")\nwhich additionally provides you a buildservice in their\ncloud (",[45,6007,6010],{"href":6008,"rel":6009},"https://web.archive.org/web/20210502104931/https://build.phonegap.com/",[49],"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: ",[45,6013,6016],{"href":6014,"rel":6015},"https://web.archive.org/web/20120512021720/http://phonegap.com/about/features/",[49],"http://www.phonegap.com/features",[41,6018,6019],{},"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.",[41,6021,6022,6023,6027,6028],{},"Then there’s Titanium (",[45,6024,6025],{"href":6025,"rel":6026},"http://www.appcelerator.com/",[49],"), 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.",[213,6029],{"alt":6030,"src":6031},"\"titanium_logo\"","https://media.synyx.de/uploads//2011/05/titanium_logo.png",[41,6033,6034],{},"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.",[41,6036,6037],{},"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.",[41,6039,6040],{},"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:",[67,6042,6044],{"className":1248,"code":6043,"language":1250,"meta":11,"style":11},"\u003Cdiv class=\"page\" id=\"page1\">...\u003C/div>\n\u003Cdiv class=\"page\" id=\"page2\">...\u003C/div>\n",[73,6045,6046,6073],{"__ignoreMap":11},[76,6047,6048,6050,6052,6054,6056,6059,6061,6063,6066,6069,6071],{"class":78,"line":79},[76,6049,1258],{"class":1257},[76,6051,4095],{"class":3921},[76,6053,4098],{"class":3925},[76,6055,3959],{"class":1257},[76,6057,6058],{"class":3962},"\"page\"",[76,6060,4241],{"class":3925},[76,6062,3959],{"class":1257},[76,6064,6065],{"class":3962},"\"page1\"",[76,6067,6068],{"class":1257},">...\u003C/",[76,6070,4095],{"class":3921},[76,6072,1265],{"class":1257},[76,6074,6075,6077,6079,6081,6083,6085,6087,6089,6092,6094,6096],{"class":78,"line":12},[76,6076,1258],{"class":1257},[76,6078,4095],{"class":3921},[76,6080,4098],{"class":3925},[76,6082,3959],{"class":1257},[76,6084,6058],{"class":3962},[76,6086,4241],{"class":3925},[76,6088,3959],{"class":1257},[76,6090,6091],{"class":3962},"\"page2\"",[76,6093,6068],{"class":1257},[76,6095,4095],{"class":3921},[76,6097,1265],{"class":1257},[41,6099,6100,6101,6106],{},"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 (",[45,6102,6105],{"href":6103,"rel":6104},"https://jqueryui.com/",[49],"jQueryUI","‘s hide- and show-methods for example).",[67,6108,6112],{"className":6109,"code":6110,"language":6111,"meta":11,"style":11},"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",[73,6113,6114,6148,6170,6178,6183,6188,6193,6201,6206,6224,6230,6234,6239,6243,6249,6253],{"__ignoreMap":11},[76,6115,6116,6120,6123,6126,6130,6132,6135,6137,6140,6142,6145],{"class":78,"line":79},[76,6117,6119],{"class":6118},"szBVR","function",[76,6121,6122],{"class":3925}," switchView",[76,6124,6125],{"class":1257},"(",[76,6127,6129],{"class":6128},"s4XuR","hideView",[76,6131,5226],{"class":1257},[76,6133,6134],{"class":6128},"showView",[76,6136,5226],{"class":1257},[76,6138,6139],{"class":6128},"hideDirection",[76,6141,5226],{"class":1257},[76,6143,6144],{"class":6128},"showDirection",[76,6146,6147],{"class":1257},") {\n",[76,6149,6150,6153,6155,6158,6161,6164,6167],{"class":78,"line":12},[76,6151,6152],{"class":3925}," $",[76,6154,6125],{"class":1257},[76,6156,6157],{"class":3962},"\"#\"",[76,6159,6160],{"class":6118}," +",[76,6162,6163],{"class":1257}," hideView).",[76,6165,6166],{"class":3925},"hide",[76,6168,6169],{"class":1257},"(\n",[76,6171,6172,6175],{"class":78,"line":90},[76,6173,6174],{"class":3962}," \"slide\"",[76,6176,6177],{"class":1257},",\n",[76,6179,6180],{"class":78,"line":96},[76,6181,6182],{"class":1257}," {\n",[76,6184,6185],{"class":78,"line":102},[76,6186,6187],{"class":1257}," direction: hideDirection,\n",[76,6189,6190],{"class":78,"line":108},[76,6191,6192],{"class":1257}," },\n",[76,6194,6195,6199],{"class":78,"line":114},[76,6196,6198],{"class":6197},"sj4cs"," 250",[76,6200,6177],{"class":1257},[76,6202,6203],{"class":78,"line":120},[76,6204,6205],{"class":1257}," );\n",[76,6207,6208,6210,6212,6214,6216,6219,6222],{"class":78,"line":125},[76,6209,6152],{"class":3925},[76,6211,6125],{"class":1257},[76,6213,6157],{"class":3962},[76,6215,6160],{"class":6118},[76,6217,6218],{"class":1257}," showView).",[76,6220,6221],{"class":3925},"show",[76,6223,6169],{"class":1257},[76,6225,6226,6228],{"class":78,"line":130},[76,6227,6174],{"class":3962},[76,6229,6177],{"class":1257},[76,6231,6232],{"class":78,"line":136},[76,6233,6182],{"class":1257},[76,6235,6236],{"class":78,"line":142},[76,6237,6238],{"class":1257}," direction: showDirection,\n",[76,6240,6241],{"class":78,"line":147},[76,6242,6192],{"class":1257},[76,6244,6245,6247],{"class":78,"line":152},[76,6246,6198],{"class":6197},[76,6248,6177],{"class":1257},[76,6250,6251],{"class":78,"line":158},[76,6252,6205],{"class":1257},[76,6254,6255],{"class":78,"line":164},[76,6256,287],{"class":1257},[41,6258,6259],{},"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.",[41,6261,6262],{},"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.",[41,6264,6265],{},"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).",[41,6267,6268],{},"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.",[41,6270,6271],{},"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.",[295,6273,6274],{},"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":11,"searchDepth":12,"depth":12,"links":6276},[],[1791],"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":5983,"description":5992},"blog/evaluating-mobile-multiplatform-frameworks",[314,6286,5517,6287],"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":6291,"title":6292,"author":6293,"body":6294,"category":6392,"date":6394,"description":11,"extension":16,"link":6395,"meta":6396,"navigation":23,"path":6397,"seo":6398,"slug":6399,"stem":6400,"tags":6401,"teaser":6405,"__hash__":6406},"blog/blog/resolutions-on-android.md","Android resolution and layout problems",[26],{"type":8,"value":6295,"toc":6386},[6296,6299,6305,6309,6312,6315,6318,6321,6324,6328,6331,6334,6342,6345,6349,6352,6355,6358,6361,6364,6367,6370,6373,6376,6380,6383],[37,6297,6292],{"id":6298},"android-resolution-and-layout-problems",[41,6300,6301],{},[213,6302],{"alt":6303,"src":6304},"I Think I Spider","https://media.synyx.de/uploads//2010/07/512px.png",[56,6306,6308],{"id":6307},"dp-or-not-dp","dp or not dp?",[41,6310,6311],{},"It was some work, but after a little time, the layout fitted for each density – well, at least so it seemed…",[41,6313,6314],{},"It fitted only for the three default dpi values (120, 160 and 240).",[41,6316,6317],{},"If you used a larger / smaller screen with the same density, the positions didn’t match exactly any more.",[41,6319,6320],{},"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.",[41,6322,6323],{},"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…).",[56,6325,6327],{"id":6326},"changing-the-layout-in-your-code","Changing the layout in your code",[41,6329,6330],{},"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.",[41,6332,6333],{},"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:",[67,6335,6340],{"className":6336,"code":6338,"language":6339},[6337],"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",[73,6341,6338],{"__ignoreMap":11},[41,6343,6344],{},"Well, maybe it isn’t a good solution, but we didn’t find any other possibility here.",[56,6346,6348],{"id":6347},"using-resource-qualifiers-for-the-different-resolutions","Using resource qualifiers for the different resolutions",[41,6350,6351],{},"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.",[41,6353,6354],{},"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.",[41,6356,6357],{},"The layouts we declare are seperated in the following folders:",[41,6359,6360],{},"layout-normal-mdpi -> 320×480",[41,6362,6363],{},"layout-normal-hdpi -> 800×480 and 854×480 (adjusted in the code)",[41,6365,6366],{},"layout-normal-ldpi -> 400×240",[41,6368,6369],{},"layout-large-mdpi -> 800×480 tablet (mostly the same as the layout-normal-hdpi layout, but needs to be in this\nfolder)",[41,6371,6372],{},"layout-small-ldpi -> 320×240",[41,6374,6375],{},"Also one -landscape folder for each of them for the landscape layout for the widget (the rest of the app is portrait\nonly)",[56,6377,6379],{"id":6378},"conclusion","Conclusion",[41,6381,6382],{},"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.",[41,6384,6385],{},"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":11,"searchDepth":12,"depth":12,"links":6387},[6388,6389,6390,6391],{"id":6307,"depth":12,"text":6308},{"id":6326,"depth":12,"text":6327},{"id":6347,"depth":12,"text":6348},{"id":6378,"depth":12,"text":6379},[1791,6393,1792],"our-apps","2010-09-08T13:28:24","https://synyx.de/blog/resolutions-on-android/",{},"/blog/resolutions-on-android",{"title":6292,"description":11},"resolutions-on-android","blog/resolutions-on-android",[314,6402,6403,3303,6404],"i-think-i-spider","ithinkispider","resolution","During the process of developing I think I spider we discovered various problems regarding Android’s different resolutions. Here you can see which problems we encountered and how we solved them:…","zE1O2G5yrUPOJAy03zZlQOFstFCOfkCJc5_scwvunN8",{"id":6408,"title":6409,"author":6410,"body":6411,"category":6733,"date":6734,"description":6735,"extension":16,"link":6736,"meta":6737,"navigation":23,"path":6738,"seo":6739,"slug":6415,"stem":6741,"tags":6742,"teaser":6746,"__hash__":6747},"blog/blog/sending-apple-push-notifications-with-notnoops-java-apns-library.md","Sending Apple Push Notifications with notnoop's java-apns library",[26],{"type":8,"value":6412,"toc":6731},[6413,6416,6431,6439,6442,6449,6452,6726,6729],[37,6414,6409],{"id":6415},"sending-apple-push-notifications-with-notnoops-java-apns-library",[41,6417,6418,6419,6424,6425,6430],{},"If you need to send apple push notifications to your users, like we do in\na ",[45,6420,6423],{"href":6421,"rel":6422},"http://mobile.synyx.de/2010/07/i-think-i-spider/",[49],"secret project"," mentioned earlier this\nweek, ",[45,6426,6429],{"href":6427,"rel":6428},"http://github.com/notnoop/java-apns",[49],"notnoop’s java-apns library"," is a good choice, because its really simple to\nuse and saves you a lot of work.",[41,6432,6433,6434,1166],{},"(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: ",[45,6435,6438],{"href":6436,"rel":6437},"http://developer.apple.com/iphone/library/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/ApplePushService/ApplePushService.html",[49],"Apple Push Service",[41,6440,6441],{},"First you have to download the library (or add it in your maven dependencies):",[41,6443,6444],{},[45,6445,6448],{"href":6446,"rel":6447},"http://github.com/notnoop/java-apns/tree/",[49],"java-apns",[41,6450,6451],{},"The programming part isn’t that much so here’s how you do it:",[67,6453,6455],{"className":226,"code":6454,"language":228,"meta":11,"style":11},"\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",[73,6456,6457,6461,6466,6471,6475,6480,6485,6490,6495,6500,6505,6510,6515,6520,6525,6530,6535,6540,6545,6550,6555,6560,6565,6570,6575,6580,6585,6590,6595,6600,6605,6610,6615,6620,6625,6630,6634,6639,6644,6649,6654,6659,6665,6670,6675,6680,6686,6692,6698,6704,6710,6716,6721],{"__ignoreMap":11},[76,6458,6459],{"class":78,"line":79},[76,6460,368],{"emptyLinePlaceholder":23},[76,6462,6463],{"class":78,"line":12},[76,6464,6465],{},"public void pushMessage() {\n",[76,6467,6468],{"class":78,"line":90},[76,6469,6470],{}," ApnsService service = null;\n",[76,6472,6473],{"class":78,"line":96},[76,6474,4385],{},[76,6476,6477],{"class":78,"line":102},[76,6478,6479],{}," // get the certificate\n",[76,6481,6482],{"class":78,"line":108},[76,6483,6484],{}," InputStream certStream = this.getClass().getClassLoader().getResourceAsStream(\"your_certificate.p12\");\n",[76,6486,6487],{"class":78,"line":114},[76,6488,6489],{}," service = APNS.newService().withCert(certStream, \"your_cert_password\").withSandboxDestination().build();\n",[76,6491,6492],{"class":78,"line":120},[76,6493,6494],{}," // or\n",[76,6496,6497],{"class":78,"line":125},[76,6498,6499],{}," // service = APNS.newService().withCert(certStream,\n",[76,6501,6502],{"class":78,"line":130},[76,6503,6504],{}," // \"your_cert_password\").withProductionDestination().build();\n",[76,6506,6507],{"class":78,"line":136},[76,6508,6509],{}," service.start();\n",[76,6511,6512],{"class":78,"line":142},[76,6513,6514],{}," // You have to delete the devices from you list that no longer\n",[76,6516,6517],{"class":78,"line":147},[76,6518,6519],{}," //have the app installed, see method below\n",[76,6521,6522],{"class":78,"line":152},[76,6523,6524],{}," deleteInactiveDevices(service);\n",[76,6526,6527],{"class":78,"line":158},[76,6528,6529],{}," // read your user list\n",[76,6531,6532],{"class":78,"line":164},[76,6533,6534],{}," List\u003CUser> userList = userDao.readUsers();\n",[76,6536,6537],{"class":78,"line":169},[76,6538,6539],{}," for (User user : userList) {\n",[76,6541,6542],{"class":78,"line":174},[76,6543,6544],{}," try {\n",[76,6546,6547],{"class":78,"line":180},[76,6548,6549],{}," // we had a daily update here, so we need to know how many\n",[76,6551,6552],{"class":78,"line":186},[76,6553,6554],{}," //days the user hasn't started the app\n",[76,6556,6557],{"class":78,"line":191},[76,6558,6559],{}," // so that we get the number of updates to display it as the badge.\n",[76,6561,6562],{"class":78,"line":196},[76,6563,6564],{}," int days = (int) ((System.currentTimeMillis() - user.getLastUpdate()) / 1000 / 60 / 60 / 24);\n",[76,6566,6567],{"class":78,"line":558},[76,6568,6569],{}," PayloadBuilder payloadBuilder = APNS.newPayload();\n",[76,6571,6572],{"class":78,"line":563},[76,6573,6574],{}," payloadBuilder = payloadBuilder.badge(days).alertBody(\"some message you want to send here\");\n",[76,6576,6577],{"class":78,"line":568},[76,6578,6579],{}," // check if the message is too long (it won't be sent if it is)\n",[76,6581,6582],{"class":78,"line":573},[76,6583,6584],{}," //and trim it if it is.\n",[76,6586,6587],{"class":78,"line":579},[76,6588,6589],{}," if (payloadBuilder.isTooLong()) {\n",[76,6591,6592],{"class":78,"line":585},[76,6593,6594],{}," payloadBuilder = payloadBuilder.shrinkBody();\n",[76,6596,6597],{"class":78,"line":590},[76,6598,6599],{}," }\n",[76,6601,6602],{"class":78,"line":1620},[76,6603,6604],{}," String payload = payloadBuilder.build();\n",[76,6606,6607],{"class":78,"line":1625},[76,6608,6609],{}," String token = user.getToken();\n",[76,6611,6612],{"class":78,"line":1630},[76,6613,6614],{}," service.push(token, payload);\n",[76,6616,6617],{"class":78,"line":1636},[76,6618,6619],{}," } catch (Exception ex) {\n",[76,6621,6622],{"class":78,"line":1641},[76,6623,6624],{}," // some logging stuff\n",[76,6626,6627],{"class":78,"line":1647},[76,6628,6629],{}," }\n",[76,6631,6632],{"class":78,"line":1652},[76,6633,5461],{},[76,6635,6636],{"class":78,"line":1657},[76,6637,6638],{}," } catch (Exception ex) {\n",[76,6640,6641],{"class":78,"line":1662},[76,6642,6643],{}," // more logging\n",[76,6645,6646],{"class":78,"line":1667},[76,6647,6648],{}," } finally {\n",[76,6650,6651],{"class":78,"line":1672},[76,6652,6653],{}," // check if the service was successfull initialized and stop it here, if it was\n",[76,6655,6656],{"class":78,"line":1678},[76,6657,6658],{}," if (service != null) {\n",[76,6660,6662],{"class":78,"line":6661},42,[76,6663,6664],{}," service.stop();\n",[76,6666,6668],{"class":78,"line":6667},43,[76,6669,5461],{},[76,6671,6673],{"class":78,"line":6672},44,[76,6674,105],{},[76,6676,6678],{"class":78,"line":6677},45,[76,6679,199],{},[76,6681,6683],{"class":78,"line":6682},46,[76,6684,6685],{}," private void deleteInactiveDevices(ApnsService service) {\n",[76,6687,6689],{"class":78,"line":6688},47,[76,6690,6691],{}," // get the list of the devices that no longer have your app installed from apple\n",[76,6693,6695],{"class":78,"line":6694},48,[76,6696,6697],{}," //ignore the =\"\" after Date here, it's a bug...\n",[76,6699,6701],{"class":78,"line":6700},49,[76,6702,6703],{}," Map\u003CString, Date> inactiveDevices = service.getInactiveDevices();\n",[76,6705,6707],{"class":78,"line":6706},50,[76,6708,6709],{}," for (String deviceToken : inactiveDevices.keySet()) {\n",[76,6711,6713],{"class":78,"line":6712},51,[76,6714,6715],{}," userDao.deleteByDeviceId(deviceToken);\n",[76,6717,6719],{"class":78,"line":6718},52,[76,6720,105],{},[76,6722,6724],{"class":78,"line":6723},53,[76,6725,199],{},[41,6727,6728],{},"Now wasn’t that an easy one this time?",[295,6730,297],{},{"title":11,"searchDepth":12,"depth":12,"links":6732},[],[1791],"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":6409,"description":6740},"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",[6743,6744,6745,4492],"apn","apple","iphone","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":6749,"title":6750,"author":6751,"body":6752,"category":7903,"date":7904,"description":7905,"extension":16,"link":7906,"meta":7907,"navigation":23,"path":7908,"seo":7909,"slug":6756,"stem":7910,"tags":7911,"teaser":7917,"__hash__":7918},"blog/blog/android-and-self-signed-ssl-certificates.md","Android and self-signed ssl certificates",[26],{"type":8,"value":6753,"toc":7901},[6754,6757,6760,6763,6766,7352,7355,7670,7679,7682,7711,7853,7896,7899],[37,6755,6750],{"id":6756},"android-and-self-signed-ssl-certificates",[41,6758,6759],{},"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.",[41,6761,6762],{},"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.",[41,6764,6765],{},"First you have to create the SSLFactory:",[67,6767,6769],{"className":226,"code":6768,"language":228,"meta":11,"style":11},"\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",[73,6770,6771,6775,6780,6785,6790,6795,6800,6805,6810,6815,6820,6825,6829,6834,6839,6844,6849,6854,6859,6864,6869,6874,6879,6884,6889,6894,6899,6904,6909,6914,6919,6924,6929,6934,6939,6943,6948,6953,6958,6962,6967,6972,6977,6981,6986,6991,6996,7001,7006,7010,7014,7019,7024,7029,7034,7040,7045,7050,7056,7062,7068,7074,7080,7086,7092,7098,7104,7110,7116,7122,7128,7133,7139,7145,7150,7156,7162,7168,7173,7178,7184,7189,7195,7201,7206,7211,7217,7222,7228,7234,7239,7244,7250,7256,7261,7267,7273,7279,7284,7290,7296,7302,7308,7313,7319,7325,7330,7336,7342,7347],{"__ignoreMap":11},[76,6772,6773],{"class":78,"line":79},[76,6774,368],{"emptyLinePlaceholder":23},[76,6776,6777],{"class":78,"line":12},[76,6778,6779],{},"/*\n",[76,6781,6782],{"class":78,"line":90},[76,6783,6784],{}," * Licensed to the Apache Software Foundation (ASF) under one\n",[76,6786,6787],{"class":78,"line":96},[76,6788,6789],{}," * or more contributor license agreements. See the NOTICE file\n",[76,6791,6792],{"class":78,"line":102},[76,6793,6794],{}," * distributed with this work for additional information\n",[76,6796,6797],{"class":78,"line":108},[76,6798,6799],{}," * regarding copyright ownership. The ASF licenses this file\n",[76,6801,6802],{"class":78,"line":114},[76,6803,6804],{}," * to you under the Apache License, Version 2.0 (the\n",[76,6806,6807],{"class":78,"line":120},[76,6808,6809],{}," * \"License\"); you may not use this file except in compliance\n",[76,6811,6812],{"class":78,"line":125},[76,6813,6814],{}," * with the License. You may obtain a copy of the License at\n",[76,6816,6817],{"class":78,"line":130},[76,6818,6819],{}," *\n",[76,6821,6822],{"class":78,"line":136},[76,6823,6824],{}," * http://www.apache.org/licenses/LICENSE-2.0\n",[76,6826,6827],{"class":78,"line":142},[76,6828,6819],{},[76,6830,6831],{"class":78,"line":147},[76,6832,6833],{}," * Unless required by applicable law or agreed to in writing,\n",[76,6835,6836],{"class":78,"line":152},[76,6837,6838],{}," * software distributed under the License is distributed on an\n",[76,6840,6841],{"class":78,"line":158},[76,6842,6843],{}," * \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n",[76,6845,6846],{"class":78,"line":164},[76,6847,6848],{}," * KIND, either express or implied. See the License for the\n",[76,6850,6851],{"class":78,"line":169},[76,6852,6853],{}," * specific language governing permissions and limitations\n",[76,6855,6856],{"class":78,"line":174},[76,6857,6858],{}," * under the License.\n",[76,6860,6861],{"class":78,"line":180},[76,6862,6863],{}," */\n",[76,6865,6866],{"class":78,"line":186},[76,6867,6868],{},"import java.io.IOException;\n",[76,6870,6871],{"class":78,"line":191},[76,6872,6873],{},"import java.net.InetAddress;\n",[76,6875,6876],{"class":78,"line":196},[76,6877,6878],{},"import java.net.InetSocketAddress;\n",[76,6880,6881],{"class":78,"line":558},[76,6882,6883],{},"import java.net.Socket;\n",[76,6885,6886],{"class":78,"line":563},[76,6887,6888],{},"import java.net.UnknownHostException;\n",[76,6890,6891],{"class":78,"line":568},[76,6892,6893],{},"import javax.net.ssl.SSLContext;\n",[76,6895,6896],{"class":78,"line":573},[76,6897,6898],{},"import javax.net.ssl.SSLSocket;\n",[76,6900,6901],{"class":78,"line":579},[76,6902,6903],{},"import javax.net.ssl.TrustManager;\n",[76,6905,6906],{"class":78,"line":585},[76,6907,6908],{},"import org.apache.http.conn.ConnectTimeoutException;\n",[76,6910,6911],{"class":78,"line":590},[76,6912,6913],{},"import org.apache.http.conn.scheme.LayeredSocketFactory;\n",[76,6915,6916],{"class":78,"line":1620},[76,6917,6918],{},"import org.apache.http.conn.scheme.SocketFactory;\n",[76,6920,6921],{"class":78,"line":1625},[76,6922,6923],{},"import org.apache.http.params.HttpConnectionParams;\n",[76,6925,6926],{"class":78,"line":1630},[76,6927,6928],{},"import org.apache.http.params.HttpParams;\n",[76,6930,6931],{"class":78,"line":1636},[76,6932,6933],{},"/**\n",[76,6935,6936],{"class":78,"line":1641},[76,6937,6938],{}," * This socket factory will create ssl socket that accepts self signed certificate\n",[76,6940,6941],{"class":78,"line":1647},[76,6942,6819],{},[76,6944,6945],{"class":78,"line":1652},[76,6946,6947],{}," * @author olamy\n",[76,6949,6950],{"class":78,"line":1657},[76,6951,6952],{}," * @version $Id: EasySSLSocketFactory.java 765355 2009-04-15 20:59:07Z evenisse $\n",[76,6954,6955],{"class":78,"line":1662},[76,6956,6957],{}," * @since 1.2.3\n",[76,6959,6960],{"class":78,"line":1667},[76,6961,6863],{},[76,6963,6964],{"class":78,"line":1672},[76,6965,6966],{},"public class EasySSLSocketFactory implements SocketFactory, LayeredSocketFactory {\n",[76,6968,6969],{"class":78,"line":1678},[76,6970,6971],{}," private SSLContext sslcontext = null;\n",[76,6973,6974],{"class":78,"line":6661},[76,6975,6976],{}," private static SSLContext createEasySSLContext() throws IOException {\n",[76,6978,6979],{"class":78,"line":6667},[76,6980,4385],{},[76,6982,6983],{"class":78,"line":6672},[76,6984,6985],{}," SSLContext context = SSLContext.getInstance(\"TLS\");\n",[76,6987,6988],{"class":78,"line":6677},[76,6989,6990],{}," context.init(null, new TrustManager[] { new EasyX509TrustManager(null) }, null);\n",[76,6992,6993],{"class":78,"line":6682},[76,6994,6995],{}," return context;\n",[76,6997,6998],{"class":78,"line":6688},[76,6999,7000],{}," } catch (Exception e) {\n",[76,7002,7003],{"class":78,"line":6694},[76,7004,7005],{}," throw new IOException(e.getMessage());\n",[76,7007,7008],{"class":78,"line":6700},[76,7009,105],{},[76,7011,7012],{"class":78,"line":6706},[76,7013,199],{},[76,7015,7016],{"class":78,"line":6712},[76,7017,7018],{}," private SSLContext getSSLContext() throws IOException {\n",[76,7020,7021],{"class":78,"line":6718},[76,7022,7023],{}," if (this.sslcontext == null) {\n",[76,7025,7026],{"class":78,"line":6723},[76,7027,7028],{}," this.sslcontext = createEasySSLContext();\n",[76,7030,7032],{"class":78,"line":7031},54,[76,7033,105],{},[76,7035,7037],{"class":78,"line":7036},55,[76,7038,7039],{}," return this.sslcontext;\n",[76,7041,7043],{"class":78,"line":7042},56,[76,7044,199],{},[76,7046,7048],{"class":78,"line":7047},57,[76,7049,880],{},[76,7051,7053],{"class":78,"line":7052},58,[76,7054,7055],{}," * @see org.apache.http.conn.scheme.SocketFactory#connectSocket(java.net.Socket, java.lang.String, int,\n",[76,7057,7059],{"class":78,"line":7058},59,[76,7060,7061],{}," * java.net.InetAddress, int, org.apache.http.params.HttpParams)\n",[76,7063,7065],{"class":78,"line":7064},60,[76,7066,7067],{}," */\n",[76,7069,7071],{"class":78,"line":7070},61,[76,7072,7073],{}," public Socket connectSocket(Socket sock, String host, int port, InetAddress localAddress, int localPort,\n",[76,7075,7077],{"class":78,"line":7076},62,[76,7078,7079],{}," HttpParams params) throws IOException, UnknownHostException, ConnectTimeoutException {\n",[76,7081,7083],{"class":78,"line":7082},63,[76,7084,7085],{}," int connTimeout = HttpConnectionParams.getConnectionTimeout(params);\n",[76,7087,7089],{"class":78,"line":7088},64,[76,7090,7091],{}," int soTimeout = HttpConnectionParams.getSoTimeout(params);\n",[76,7093,7095],{"class":78,"line":7094},65,[76,7096,7097],{}," InetSocketAddress remoteAddress = new InetSocketAddress(host, port);\n",[76,7099,7101],{"class":78,"line":7100},66,[76,7102,7103],{}," SSLSocket sslsock = (SSLSocket) ((sock != null) ? sock : createSocket());\n",[76,7105,7107],{"class":78,"line":7106},67,[76,7108,7109],{}," if ((localAddress != null) || (localPort > 0)) {\n",[76,7111,7113],{"class":78,"line":7112},68,[76,7114,7115],{}," // we need to bind explicitly\n",[76,7117,7119],{"class":78,"line":7118},69,[76,7120,7121],{}," if (localPort \u003C 0) {\n",[76,7123,7125],{"class":78,"line":7124},70,[76,7126,7127],{}," localPort = 0; // indicates \"any\"\n",[76,7129,7131],{"class":78,"line":7130},71,[76,7132,5461],{},[76,7134,7136],{"class":78,"line":7135},72,[76,7137,7138],{}," InetSocketAddress isa = new InetSocketAddress(localAddress, localPort);\n",[76,7140,7142],{"class":78,"line":7141},73,[76,7143,7144],{}," sslsock.bind(isa);\n",[76,7146,7148],{"class":78,"line":7147},74,[76,7149,105],{},[76,7151,7153],{"class":78,"line":7152},75,[76,7154,7155],{}," sslsock.connect(remoteAddress, connTimeout);\n",[76,7157,7159],{"class":78,"line":7158},76,[76,7160,7161],{}," sslsock.setSoTimeout(soTimeout);\n",[76,7163,7165],{"class":78,"line":7164},77,[76,7166,7167],{}," return sslsock;\n",[76,7169,7171],{"class":78,"line":7170},78,[76,7172,199],{},[76,7174,7176],{"class":78,"line":7175},79,[76,7177,880],{},[76,7179,7181],{"class":78,"line":7180},80,[76,7182,7183],{}," * @see org.apache.http.conn.scheme.SocketFactory#createSocket()\n",[76,7185,7187],{"class":78,"line":7186},81,[76,7188,7067],{},[76,7190,7192],{"class":78,"line":7191},82,[76,7193,7194],{}," public Socket createSocket() throws IOException {\n",[76,7196,7198],{"class":78,"line":7197},83,[76,7199,7200],{}," return getSSLContext().getSocketFactory().createSocket();\n",[76,7202,7204],{"class":78,"line":7203},84,[76,7205,199],{},[76,7207,7209],{"class":78,"line":7208},85,[76,7210,880],{},[76,7212,7214],{"class":78,"line":7213},86,[76,7215,7216],{}," * @see org.apache.http.conn.scheme.SocketFactory#isSecure(java.net.Socket)\n",[76,7218,7220],{"class":78,"line":7219},87,[76,7221,7067],{},[76,7223,7225],{"class":78,"line":7224},88,[76,7226,7227],{}," public boolean isSecure(Socket socket) throws IllegalArgumentException {\n",[76,7229,7231],{"class":78,"line":7230},89,[76,7232,7233],{}," return true;\n",[76,7235,7237],{"class":78,"line":7236},90,[76,7238,199],{},[76,7240,7242],{"class":78,"line":7241},91,[76,7243,880],{},[76,7245,7247],{"class":78,"line":7246},92,[76,7248,7249],{}," * @see org.apache.http.conn.scheme.LayeredSocketFactory#createSocket(java.net.Socket, java.lang.String, int,\n",[76,7251,7253],{"class":78,"line":7252},93,[76,7254,7255],{}," * boolean)\n",[76,7257,7259],{"class":78,"line":7258},94,[76,7260,7067],{},[76,7262,7264],{"class":78,"line":7263},95,[76,7265,7266],{}," public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException,\n",[76,7268,7270],{"class":78,"line":7269},96,[76,7271,7272],{}," UnknownHostException {\n",[76,7274,7276],{"class":78,"line":7275},97,[76,7277,7278],{}," return getSSLContext().getSocketFactory().createSocket(socket, host, port, autoClose);\n",[76,7280,7282],{"class":78,"line":7281},98,[76,7283,199],{},[76,7285,7287],{"class":78,"line":7286},99,[76,7288,7289],{}," // -------------------------------------------------------------------\n",[76,7291,7293],{"class":78,"line":7292},100,[76,7294,7295],{}," // javadoc in org.apache.http.conn.scheme.SocketFactory says :\n",[76,7297,7299],{"class":78,"line":7298},101,[76,7300,7301],{}," // Both Object.equals() and Object.hashCode() must be overridden\n",[76,7303,7305],{"class":78,"line":7304},102,[76,7306,7307],{}," // for the correct operation of some connection managers\n",[76,7309,7311],{"class":78,"line":7310},103,[76,7312,7289],{},[76,7314,7316],{"class":78,"line":7315},104,[76,7317,7318],{}," public boolean equals(Object obj) {\n",[76,7320,7322],{"class":78,"line":7321},105,[76,7323,7324],{}," return ((obj != null) && obj.getClass().equals(EasySSLSocketFactory.class));\n",[76,7326,7328],{"class":78,"line":7327},106,[76,7329,199],{},[76,7331,7333],{"class":78,"line":7332},107,[76,7334,7335],{}," public int hashCode() {\n",[76,7337,7339],{"class":78,"line":7338},108,[76,7340,7341],{}," return EasySSLSocketFactory.class.hashCode();\n",[76,7343,7345],{"class":78,"line":7344},109,[76,7346,199],{},[76,7348,7350],{"class":78,"line":7349},110,[76,7351,287],{},[41,7353,7354],{},"And the TrustManager:",[67,7356,7358],{"className":226,"code":7357,"language":228,"meta":11,"style":11},"\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",[73,7359,7360,7364,7368,7372,7376,7380,7384,7388,7392,7396,7400,7404,7408,7412,7416,7420,7424,7428,7432,7436,7441,7446,7451,7456,7461,7465,7470,7475,7479,7483,7488,7492,7496,7501,7506,7510,7515,7519,7524,7529,7534,7539,7544,7549,7554,7558,7563,7567,7571,7576,7580,7585,7590,7594,7598,7603,7607,7612,7617,7622,7626,7631,7635,7639,7643,7648,7652,7657,7662,7666],{"__ignoreMap":11},[76,7361,7362],{"class":78,"line":79},[76,7363,368],{"emptyLinePlaceholder":23},[76,7365,7366],{"class":78,"line":12},[76,7367,6779],{},[76,7369,7370],{"class":78,"line":90},[76,7371,6784],{},[76,7373,7374],{"class":78,"line":96},[76,7375,6789],{},[76,7377,7378],{"class":78,"line":102},[76,7379,6794],{},[76,7381,7382],{"class":78,"line":108},[76,7383,6799],{},[76,7385,7386],{"class":78,"line":114},[76,7387,6804],{},[76,7389,7390],{"class":78,"line":120},[76,7391,6809],{},[76,7393,7394],{"class":78,"line":125},[76,7395,6814],{},[76,7397,7398],{"class":78,"line":130},[76,7399,6819],{},[76,7401,7402],{"class":78,"line":136},[76,7403,6824],{},[76,7405,7406],{"class":78,"line":142},[76,7407,6819],{},[76,7409,7410],{"class":78,"line":147},[76,7411,6833],{},[76,7413,7414],{"class":78,"line":152},[76,7415,6838],{},[76,7417,7418],{"class":78,"line":158},[76,7419,6843],{},[76,7421,7422],{"class":78,"line":164},[76,7423,6848],{},[76,7425,7426],{"class":78,"line":169},[76,7427,6853],{},[76,7429,7430],{"class":78,"line":174},[76,7431,6858],{},[76,7433,7434],{"class":78,"line":180},[76,7435,6863],{},[76,7437,7438],{"class":78,"line":186},[76,7439,7440],{},"import java.security.KeyStore;\n",[76,7442,7443],{"class":78,"line":191},[76,7444,7445],{},"import java.security.KeyStoreException;\n",[76,7447,7448],{"class":78,"line":196},[76,7449,7450],{},"import java.security.NoSuchAlgorithmException;\n",[76,7452,7453],{"class":78,"line":558},[76,7454,7455],{},"import java.security.cert.CertificateException;\n",[76,7457,7458],{"class":78,"line":563},[76,7459,7460],{},"import java.security.cert.X509Certificate;\n",[76,7462,7463],{"class":78,"line":568},[76,7464,6903],{},[76,7466,7467],{"class":78,"line":573},[76,7468,7469],{},"import javax.net.ssl.TrustManagerFactory;\n",[76,7471,7472],{"class":78,"line":579},[76,7473,7474],{},"import javax.net.ssl.X509TrustManager;\n",[76,7476,7477],{"class":78,"line":585},[76,7478,6933],{},[76,7480,7481],{"class":78,"line":590},[76,7482,6947],{},[76,7484,7485],{"class":78,"line":1620},[76,7486,7487],{}," * @version $Id: EasyX509TrustManager.java 765355 2009-04-15 20:59:07Z evenisse $\n",[76,7489,7490],{"class":78,"line":1625},[76,7491,6957],{},[76,7493,7494],{"class":78,"line":1630},[76,7495,6863],{},[76,7497,7498],{"class":78,"line":1636},[76,7499,7500],{},"public class EasyX509TrustManager implements X509TrustManager {\n",[76,7502,7503],{"class":78,"line":1641},[76,7504,7505],{}," private X509TrustManager standardTrustManager = null;\n",[76,7507,7508],{"class":78,"line":1647},[76,7509,880],{},[76,7511,7512],{"class":78,"line":1652},[76,7513,7514],{}," * Constructor for EasyX509TrustManager.\n",[76,7516,7517],{"class":78,"line":1657},[76,7518,7067],{},[76,7520,7521],{"class":78,"line":1662},[76,7522,7523],{}," public EasyX509TrustManager(KeyStore keystore) throws NoSuchAlgorithmException, KeyStoreException {\n",[76,7525,7526],{"class":78,"line":1667},[76,7527,7528],{}," super();\n",[76,7530,7531],{"class":78,"line":1672},[76,7532,7533],{}," TrustManagerFactory factory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());\n",[76,7535,7536],{"class":78,"line":1678},[76,7537,7538],{}," factory.init(keystore);\n",[76,7540,7541],{"class":78,"line":6661},[76,7542,7543],{}," TrustManager[] trustmanagers = factory.getTrustManagers();\n",[76,7545,7546],{"class":78,"line":6667},[76,7547,7548],{}," if (trustmanagers.length == 0) {\n",[76,7550,7551],{"class":78,"line":6672},[76,7552,7553],{}," throw new NoSuchAlgorithmException(\"no trust manager found\");\n",[76,7555,7556],{"class":78,"line":6677},[76,7557,105],{},[76,7559,7560],{"class":78,"line":6682},[76,7561,7562],{}," this.standardTrustManager = (X509TrustManager) trustmanagers[0];\n",[76,7564,7565],{"class":78,"line":6688},[76,7566,199],{},[76,7568,7569],{"class":78,"line":6694},[76,7570,880],{},[76,7572,7573],{"class":78,"line":6700},[76,7574,7575],{}," * @see javax.net.ssl.X509TrustManager#checkClientTrusted(X509Certificate[],String authType)\n",[76,7577,7578],{"class":78,"line":6706},[76,7579,7067],{},[76,7581,7582],{"class":78,"line":6712},[76,7583,7584],{}," public void checkClientTrusted(X509Certificate[] certificates, String authType) throws CertificateException {\n",[76,7586,7587],{"class":78,"line":6718},[76,7588,7589],{}," standardTrustManager.checkClientTrusted(certificates, authType);\n",[76,7591,7592],{"class":78,"line":6723},[76,7593,199],{},[76,7595,7596],{"class":78,"line":7031},[76,7597,880],{},[76,7599,7600],{"class":78,"line":7036},[76,7601,7602],{}," * @see javax.net.ssl.X509TrustManager#checkServerTrusted(X509Certificate[],String authType)\n",[76,7604,7605],{"class":78,"line":7042},[76,7606,7067],{},[76,7608,7609],{"class":78,"line":7047},[76,7610,7611],{}," public void checkServerTrusted(X509Certificate[] certificates, String authType) throws CertificateException {\n",[76,7613,7614],{"class":78,"line":7052},[76,7615,7616],{}," if ((certificates != null) && (certificates.length == 1)) {\n",[76,7618,7619],{"class":78,"line":7058},[76,7620,7621],{}," certificates[0].checkValidity();\n",[76,7623,7624],{"class":78,"line":7064},[76,7625,735],{},[76,7627,7628],{"class":78,"line":7070},[76,7629,7630],{}," standardTrustManager.checkServerTrusted(certificates, authType);\n",[76,7632,7633],{"class":78,"line":7076},[76,7634,105],{},[76,7636,7637],{"class":78,"line":7082},[76,7638,199],{},[76,7640,7641],{"class":78,"line":7088},[76,7642,880],{},[76,7644,7645],{"class":78,"line":7094},[76,7646,7647],{}," * @see javax.net.ssl.X509TrustManager#getAcceptedIssuers()\n",[76,7649,7650],{"class":78,"line":7100},[76,7651,7067],{},[76,7653,7654],{"class":78,"line":7106},[76,7655,7656],{}," public X509Certificate[] getAcceptedIssuers() {\n",[76,7658,7659],{"class":78,"line":7112},[76,7660,7661],{}," return this.standardTrustManager.getAcceptedIssuers();\n",[76,7663,7664],{"class":78,"line":7118},[76,7665,199],{},[76,7667,7668],{"class":78,"line":7124},[76,7669,287],{},[41,7671,7672,7673,7678],{},"(Both classes are\nfrom ",[45,7674,7677],{"href":7675,"rel":7676},"https://web.archive.org/web/20111230175916/http://exchangeit.googlecode.com:80/svn-history/r23/trunk/src/com/byarger/exchangeit/",[49],"exchangeit","\nwith a small change on the EasySSLSocketFactory to work on android 2.2)",[41,7680,7681],{},"Now we have to do some other preparations and create a HttpClient that we can use to establish the connection:",[67,7683,7685],{"className":226,"code":7684,"language":228,"meta":11,"style":11},"\n//members\nprivate ClientConnectionManager clientConnectionManager;\nprivate HttpContext context;\nprivate HttpParams params;\n\n",[73,7686,7687,7691,7696,7701,7706],{"__ignoreMap":11},[76,7688,7689],{"class":78,"line":79},[76,7690,368],{"emptyLinePlaceholder":23},[76,7692,7693],{"class":78,"line":12},[76,7694,7695],{},"//members\n",[76,7697,7698],{"class":78,"line":90},[76,7699,7700],{},"private ClientConnectionManager clientConnectionManager;\n",[76,7702,7703],{"class":78,"line":96},[76,7704,7705],{},"private HttpContext context;\n",[76,7707,7708],{"class":78,"line":102},[76,7709,7710],{},"private HttpParams params;\n",[67,7712,7714],{"className":226,"code":7713,"language":228,"meta":11,"style":11},"\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",[73,7715,7716,7720,7725,7730,7735,7739,7744,7749,7754,7759,7764,7769,7774,7779,7784,7789,7794,7799,7804,7809,7814,7819,7824,7829,7834,7839,7844,7849],{"__ignoreMap":11},[76,7717,7718],{"class":78,"line":79},[76,7719,368],{"emptyLinePlaceholder":23},[76,7721,7722],{"class":78,"line":12},[76,7723,7724],{},"//constructor\n",[76,7726,7727],{"class":78,"line":90},[76,7728,7729],{},"public WebService(){\n",[76,7731,7732],{"class":78,"line":96},[76,7733,7734],{}," setup();\n",[76,7736,7737],{"class":78,"line":102},[76,7738,287],{},[76,7740,7741],{"class":78,"line":108},[76,7742,7743],{},"//prepare for the https connection\n",[76,7745,7746],{"class":78,"line":114},[76,7747,7748],{},"//call this in the constructor of the class that does the connection if\n",[76,7750,7751],{"class":78,"line":120},[76,7752,7753],{},"//it's used multiple times\n",[76,7755,7756],{"class":78,"line":125},[76,7757,7758],{},"private void setup(){\n",[76,7760,7761],{"class":78,"line":130},[76,7762,7763],{},"SchemeRegistry schemeRegistry = new SchemeRegistry();\n",[76,7765,7766],{"class":78,"line":136},[76,7767,7768],{}," // http scheme\n",[76,7770,7771],{"class":78,"line":142},[76,7772,7773],{}," schemeRegistry.register(new Scheme(\"http\", PlainSocketFactory.getSocketFactory(), 80));\n",[76,7775,7776],{"class":78,"line":147},[76,7777,7778],{}," // https scheme\n",[76,7780,7781],{"class":78,"line":152},[76,7782,7783],{}," schemeRegistry.register(new Scheme(\"https\", new EasySSLSocketFactory(), 443));\n",[76,7785,7786],{"class":78,"line":158},[76,7787,7788],{}," params = new BasicHttpParams();\n",[76,7790,7791],{"class":78,"line":164},[76,7792,7793],{}," params.setParameter(ConnManagerPNames.MAX_TOTAL_CONNECTIONS, 1);\n",[76,7795,7796],{"class":78,"line":169},[76,7797,7798],{}," params.setParameter(ConnManagerPNames.MAX_CONNECTIONS_PER_ROUTE, new ConnPerRouteBean(1));\n",[76,7800,7801],{"class":78,"line":174},[76,7802,7803],{}," params.setParameter(HttpProtocolParams.USE_EXPECT_CONTINUE, false);\n",[76,7805,7806],{"class":78,"line":180},[76,7807,7808],{}," HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);\n",[76,7810,7811],{"class":78,"line":186},[76,7812,7813],{}," HttpProtocolParams.setContentCharset(params, \"utf8\");\n",[76,7815,7816],{"class":78,"line":191},[76,7817,7818],{}," CredentialsProvider credentialsProvider = new BasicCredentialsProvider();\n",[76,7820,7821],{"class":78,"line":196},[76,7822,7823],{}," //set the user credentials for our site \"example.com\"\n",[76,7825,7826],{"class":78,"line":558},[76,7827,7828],{}," credentialsProvider.setCredentials(new AuthScope(\"example.com\", AuthScope.ANY_PORT),\n",[76,7830,7831],{"class":78,"line":563},[76,7832,7833],{}," new UsernamePasswordCredentials(\"UserNameHere\", \"UserPasswordHere\"));\n",[76,7835,7836],{"class":78,"line":568},[76,7837,7838],{}," clientConnectionManager = new ThreadSafeClientConnManager(params, schemeRegistry);\n",[76,7840,7841],{"class":78,"line":573},[76,7842,7843],{}," context = new BasicHttpContext();\n",[76,7845,7846],{"class":78,"line":579},[76,7847,7848],{}," context.setAttribute(\"http.auth.credentials-provider\", credentialsProvider);\n",[76,7850,7851],{"class":78,"line":585},[76,7852,287],{},[67,7854,7856],{"className":226,"code":7855,"language":228,"meta":11,"style":11},"\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",[73,7857,7858,7862,7867,7872,7877,7882,7887,7892],{"__ignoreMap":11},[76,7859,7860],{"class":78,"line":79},[76,7861,368],{"emptyLinePlaceholder":23},[76,7863,7864],{"class":78,"line":12},[76,7865,7866],{},"public HttpResponse getResponseFromUrl(String url){\n",[76,7868,7869],{"class":78,"line":90},[76,7870,7871],{},"//connection (client has to be created for every new connection)\n",[76,7873,7874],{"class":78,"line":96},[76,7875,7876],{},"client = new DefaultHttpClient(clientConnectionManager, params);\n",[76,7878,7879],{"class":78,"line":102},[76,7880,7881],{},"HttpGet get = new HttpGet(url);\n",[76,7883,7884],{"class":78,"line":108},[76,7885,7886],{},"HttpResponse response = client.execute(get, context);\n",[76,7888,7889],{"class":78,"line":114},[76,7890,7891],{},"return response;\n",[76,7893,7894],{"class":78,"line":120},[76,7895,287],{},[41,7897,7898],{},"And that's it. I hope this will help some of you to solve their problems with self-signed certs!",[295,7900,297],{},{"title":11,"searchDepth":12,"depth":12,"links":7902},[],[1791,1792],"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":6750,"description":6759},"blog/android-and-self-signed-ssl-certificates",[314,7912,7913,7914,7915,7916],"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":7920,"title":7921,"author":7922,"body":7923,"category":8543,"date":8544,"description":8545,"extension":16,"link":8546,"meta":8547,"navigation":23,"path":8548,"seo":8549,"slug":7927,"stem":8551,"tags":8552,"teaser":8557,"__hash__":8558},"blog/blog/routing-driving-directions-on-android-part-2-draw-the-route.md","Routing / Driving directions on Android – Part 2: Draw the route",[26],{"type":8,"value":7924,"toc":8538},[7925,7928,7937,7941,7944,7993,7996,8069,8072,8105,8109,8112,8115,8129,8132,8137,8144,8209,8212,8346,8350,8353,8356,8437,8440,8464,8467,8470,8473,8516,8519,8533,8536],[37,7926,7921],{"id":7927},"routing-driving-directions-on-android-part-2-draw-the-route",[41,7929,7930,7931,7936],{},"After you ",[45,7932,7935],{"href":7933,"rel":7934},"http://mobile.synyx.de/2010/06/routing-driving-directions-on-android-part-1-get-the-route/",[49],"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.",[56,7938,7940],{"id":7939},"create-a-suiting-overlay","Create a suiting Overlay",[41,7942,7943],{},"We basically need a Overlay that takes two Geopoints and maybe a color in which the lines should be drawn. So here We\nhave:",[67,7945,7947],{"className":226,"code":7946,"language":228,"meta":11,"style":11},"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",[73,7948,7949,7954,7959,7964,7969,7974,7979,7984,7989],{"__ignoreMap":11},[76,7950,7951],{"class":78,"line":79},[76,7952,7953],{},"public class RouteOverlay extends Overlay {\n",[76,7955,7956],{"class":78,"line":12},[76,7957,7958],{}," private GeoPoint gp1;\n",[76,7960,7961],{"class":78,"line":90},[76,7962,7963],{}," private GeoPoint gp2;\n",[76,7965,7966],{"class":78,"line":96},[76,7967,7968],{}," private int color;\n",[76,7970,7971],{"class":78,"line":102},[76,7972,7973],{},"public RouteOverlay(GeoPoint gp1, GeoPoint gp2, int color) {\n",[76,7975,7976],{"class":78,"line":108},[76,7977,7978],{}," this.gp1 = gp1;\n",[76,7980,7981],{"class":78,"line":114},[76,7982,7983],{}," this.gp2 = gp2;\n",[76,7985,7986],{"class":78,"line":120},[76,7987,7988],{}," this.color = color;\n",[76,7990,7991],{"class":78,"line":125},[76,7992,199],{},[41,7994,7995],{},"Now all that’s left now for our Overlay is to override the draw() method and draw the line as we need it:",[67,7997,7999],{"className":226,"code":7998,"language":228,"meta":11,"style":11},"@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",[73,8000,8001,8005,8010,8015,8020,8025,8030,8035,8040,8045,8050,8055,8060,8065],{"__ignoreMap":11},[76,8002,8003],{"class":78,"line":79},[76,8004,3235],{},[76,8006,8007],{"class":78,"line":12},[76,8008,8009],{},"public void draw(Canvas canvas, MapView mapView, boolean shadow) {\n",[76,8011,8012],{"class":78,"line":90},[76,8013,8014],{}," Projection projection = mapView.getProjection();\n",[76,8016,8017],{"class":78,"line":96},[76,8018,8019],{}," Paint paint = new Paint();\n",[76,8021,8022],{"class":78,"line":102},[76,8023,8024],{}," Point point = new Point();\n",[76,8026,8027],{"class":78,"line":108},[76,8028,8029],{}," projection.toPixels(gp1, point);\n",[76,8031,8032],{"class":78,"line":114},[76,8033,8034],{}," paint.setColor(color);\n",[76,8036,8037],{"class":78,"line":120},[76,8038,8039],{}," Point point2 = new Point();\n",[76,8041,8042],{"class":78,"line":125},[76,8043,8044],{}," projection.toPixels(gp2, point2);\n",[76,8046,8047],{"class":78,"line":130},[76,8048,8049],{}," paint.setStrokeWidth(5);\n",[76,8051,8052],{"class":78,"line":136},[76,8053,8054],{}," paint.setAlpha(120);\n",[76,8056,8057],{"class":78,"line":142},[76,8058,8059],{}," canvas.drawLine(point.x, point.y, point2.x, point2.y, paint);\n",[76,8061,8062],{"class":78,"line":147},[76,8063,8064],{}," super.draw(canvas, mapView, shadow);\n",[76,8066,8067],{"class":78,"line":152},[76,8068,287],{},[41,8070,8071],{},"Back in the Activity, just iterate over the GeoPoints that you got from google maps and add each of them to the MapView:",[67,8073,8075],{"className":226,"code":8074,"language":228,"meta":11,"style":11},"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",[73,8076,8077,8082,8087,8092,8097,8101],{"__ignoreMap":11},[76,8078,8079],{"class":78,"line":79},[76,8080,8081],{},"private void drawPath(List geoPoints, int color) {\n",[76,8083,8084],{"class":78,"line":12},[76,8085,8086],{}," List overlays = mapView.getOverlays();\n",[76,8088,8089],{"class":78,"line":90},[76,8090,8091],{}," for (int i = 1; i \u003C geoPoints.size(); i++) {\n",[76,8093,8094],{"class":78,"line":96},[76,8095,8096],{}," overlays.add(new RouteOverlay(geoPoints.get(i - 1), geoPoints.get(i), color));\n",[76,8098,8099],{"class":78,"line":102},[76,8100,3727],{},[76,8102,8103],{"class":78,"line":108},[76,8104,287],{},[56,8106,8108],{"id":8107},"get-location-updates-from-the-location-manager","Get location updates from the location manager",[41,8110,8111],{},"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!",[41,8113,8114],{},"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:",[67,8116,8118],{"className":226,"code":8117,"language":228,"meta":11,"style":11},"\nlocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 300000, 5000, this);\n\n",[73,8119,8120,8124],{"__ignoreMap":11},[76,8121,8122],{"class":78,"line":79},[76,8123,368],{"emptyLinePlaceholder":23},[76,8125,8126],{"class":78,"line":12},[76,8127,8128],{},"locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 300000, 5000, this);\n",[41,8130,8131],{},"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.",[41,8133,8134],{},[903,8135,8136],{},"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)",[41,8138,8139,8140,8143],{},"Also don’t forget to ",[903,8141,8142],{},"remove the listener"," if the MapView isn’t visible:",[67,8145,8147],{"className":226,"code":8146,"language":228,"meta":11,"style":11},"\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",[73,8148,8149,8153,8157,8162,8167,8172,8177,8181,8185,8190,8195,8200,8205],{"__ignoreMap":11},[76,8150,8151],{"class":78,"line":79},[76,8152,368],{"emptyLinePlaceholder":23},[76,8154,8155],{"class":78,"line":12},[76,8156,3235],{},[76,8158,8159],{"class":78,"line":90},[76,8160,8161],{}," protected void onPause() {\n",[76,8163,8164],{"class":78,"line":96},[76,8165,8166],{}," //remove the listener\n",[76,8168,8169],{"class":78,"line":102},[76,8170,8171],{}," locationManager.removeUpdates(this);\n",[76,8173,8174],{"class":78,"line":108},[76,8175,8176],{}," super.onPause();\n",[76,8178,8179],{"class":78,"line":114},[76,8180,199],{},[76,8182,8183],{"class":78,"line":120},[76,8184,3235],{},[76,8186,8187],{"class":78,"line":125},[76,8188,8189],{}," protected void onResume() {\n",[76,8191,8192],{"class":78,"line":130},[76,8193,8194],{}," //add the listener again\n",[76,8196,8197],{"class":78,"line":136},[76,8198,8199],{}," locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 300000, 5000, this);\n",[76,8201,8202],{"class":78,"line":142},[76,8203,8204],{}," super.onResume();\n",[76,8206,8207],{"class":78,"line":147},[76,8208,199],{},[41,8210,8211],{},"Now we have to let our MapActivity implement LocationListener and react to the updates:",[67,8213,8215],{"className":226,"code":8214,"language":228,"meta":11,"style":11},"\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",[73,8216,8217,8221,8226,8230,8235,8240,8244,8249,8254,8259,8264,8269,8274,8279,8284,8289,8294,8298,8303,8308,8313,8318,8323,8328,8332,8337,8342],{"__ignoreMap":11},[76,8218,8219],{"class":78,"line":79},[76,8220,368],{"emptyLinePlaceholder":23},[76,8222,8223],{"class":78,"line":12},[76,8224,8225],{},"public class RouteActivity extends MapActivity implements LocationListener {\n",[76,8227,8228],{"class":78,"line":90},[76,8229,3235],{},[76,8231,8232],{"class":78,"line":96},[76,8233,8234],{},"public void onLocationChanged(Location location) {\n",[76,8236,8237],{"class":78,"line":102},[76,8238,8239],{},"drawUserPosition(location);\n",[76,8241,8242],{"class":78,"line":108},[76,8243,287],{},[76,8245,8246],{"class":78,"line":114},[76,8247,8248],{},"private void drawUserPosition(Location location) {\n",[76,8250,8251],{"class":78,"line":120},[76,8252,8253],{}," GeoPoint currentLocation;\n",[76,8255,8256],{"class":78,"line":125},[76,8257,8258],{}," currentLocation = new GeoPoint((int) ( location.getLatitude() * 1E6), (int) ( location\n",[76,8260,8261],{"class":78,"line":130},[76,8262,8263],{}," getLongitude() * 1E6));\n",[76,8265,8266],{"class":78,"line":136},[76,8267,8268],{}," OverlayItem currentLocationOverlay = new OverlayItem(currentLocation, getString(R.string.your_location),\n",[76,8270,8271],{"class":78,"line":142},[76,8272,8273],{}," getString(R.string.current_location));\n",[76,8275,8276],{"class":78,"line":147},[76,8277,8278],{}," mapOverlays.clear();\n",[76,8280,8281],{"class":78,"line":152},[76,8282,8283],{}," if (locationOverlays.size() > 1) {\n",[76,8285,8286],{"class":78,"line":158},[76,8287,8288],{}," // remove the old user position if there is one\n",[76,8290,8291],{"class":78,"line":164},[76,8292,8293],{}," locationOverlays.removeOverlay(1);\n",[76,8295,8296],{"class":78,"line":169},[76,8297,3727],{},[76,8299,8300],{"class":78,"line":174},[76,8301,8302],{}," //add new user position\n",[76,8304,8305],{"class":78,"line":180},[76,8306,8307],{}," locationOverlays.addOverlay(currentLocationOverlay, this.getResources().getDrawable(R.drawable.someImage));\n",[76,8309,8310],{"class":78,"line":186},[76,8311,8312],{}," mapOverlays.add(locationOverlays);\n",[76,8314,8315],{"class":78,"line":191},[76,8316,8317],{}," //.\n",[76,8319,8320],{"class":78,"line":196},[76,8321,8322],{}," //. calculate / set the mapcenter, zoom to span\n",[76,8324,8325],{"class":78,"line":558},[76,8326,8327],{}," //. see in previous posts\n",[76,8329,8330],{"class":78,"line":563},[76,8331,8317],{},[76,8333,8334],{"class":78,"line":568},[76,8335,8336],{}," RouteThread rt = new RouteThread(currentLocation, synyxGeoPoint, routeHandler);\n",[76,8338,8339],{"class":78,"line":573},[76,8340,8341],{}," rt.start();\n",[76,8343,8344],{"class":78,"line":579},[76,8345,287],{},[56,8347,8349],{"id":8348},"make-it-threaded","Make it threaded",[41,8351,8352],{},"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.",[41,8354,8355],{},"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:",[67,8357,8359],{"className":226,"code":8358,"language":228,"meta":11,"style":11},"\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",[73,8360,8361,8365,8370,8375,8380,8385,8390,8395,8400,8405,8410,8415,8420,8425,8429,8433],{"__ignoreMap":11},[76,8362,8363],{"class":78,"line":79},[76,8364,368],{"emptyLinePlaceholder":23},[76,8366,8367],{"class":78,"line":12},[76,8368,8369],{},"private class RouteHandler extends Handler {\n",[76,8371,8372],{"class":78,"line":90},[76,8373,8374],{}," public void handleMessage(Message msg) {\n",[76,8376,8377],{"class":78,"line":96},[76,8378,8379],{}," boolean error = msg.getData().getBoolean(\"error\", false);\n",[76,8381,8382],{"class":78,"line":102},[76,8383,8384],{}," if (!error) {\n",[76,8386,8387],{"class":78,"line":108},[76,8388,8389],{}," // set the geopoints (we can't just add the overlays\n",[76,8391,8392],{"class":78,"line":114},[76,8393,8394],{}," // to the map here, because it's on a different thread\n",[76,8396,8397],{"class":78,"line":120},[76,8398,8399],{}," geoPoints = (List\u003CGeoPoint>) msg.obj;\n",[76,8401,8402],{"class":78,"line":125},[76,8403,8404],{}," post(updateRoute);\n",[76,8406,8407],{"class":78,"line":130},[76,8408,8409],{}," } else {\n",[76,8411,8412],{"class":78,"line":136},[76,8413,8414],{}," // maybe you want to show an error message here to\n",[76,8416,8417],{"class":78,"line":142},[76,8418,8419],{}," // notice the user that the route can not be displayed\n",[76,8421,8422],{"class":78,"line":147},[76,8423,8424],{}," // because there's no connection to the internet\n",[76,8426,8427],{"class":78,"line":152},[76,8428,5461],{},[76,8430,8431],{"class":78,"line":158},[76,8432,105],{},[76,8434,8435],{"class":78,"line":164},[76,8436,199],{},[41,8438,8439],{},"Send the geopoints from the RouteThread:",[67,8441,8443],{"className":226,"code":8442,"language":228,"meta":11,"style":11},"\nMessage msg = new Message();\nmsg.obj = decodePoly(encoded);\nhandler.dispatchMessage(msg);\n\n",[73,8444,8445,8449,8454,8459],{"__ignoreMap":11},[76,8446,8447],{"class":78,"line":79},[76,8448,368],{"emptyLinePlaceholder":23},[76,8450,8451],{"class":78,"line":12},[76,8452,8453],{},"Message msg = new Message();\n",[76,8455,8456],{"class":78,"line":90},[76,8457,8458],{},"msg.obj = decodePoly(encoded);\n",[76,8460,8461],{"class":78,"line":96},[76,8462,8463],{},"handler.dispatchMessage(msg);\n",[41,8465,8466],{},"(If you have further data to send, use a Bundle object and add it to the Message.)",[41,8468,8469],{},"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.",[41,8471,8472],{},"First we create a runnable in the Activity:",[67,8474,8476],{"className":226,"code":8475,"language":228,"meta":11,"style":11},"\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",[73,8477,8478,8482,8487,8492,8497,8502,8507,8511],{"__ignoreMap":11},[76,8479,8480],{"class":78,"line":79},[76,8481,368],{"emptyLinePlaceholder":23},[76,8483,8484],{"class":78,"line":12},[76,8485,8486],{},"final Runnable updateRoute = new Runnable() {\n",[76,8488,8489],{"class":78,"line":90},[76,8490,8491],{}," public void run() {\n",[76,8493,8494],{"class":78,"line":96},[76,8495,8496],{}," // draw the path and then invalidate the mapview so that it redraws itself\n",[76,8498,8499],{"class":78,"line":102},[76,8500,8501],{}," drawPath(geoPoints, Color.GREEN);\n",[76,8503,8504],{"class":78,"line":108},[76,8505,8506],{}," mapView.invalidate();\n",[76,8508,8509],{"class":78,"line":114},[76,8510,105],{},[76,8512,8513],{"class":78,"line":120},[76,8514,8515],{}," };\n",[41,8517,8518],{},"And all that is left to do is to call it from inside the handler:",[67,8520,8522],{"className":226,"code":8521,"language":228,"meta":11,"style":11},"\npost(updateRoute);\n\n",[73,8523,8524,8528],{"__ignoreMap":11},[76,8525,8526],{"class":78,"line":79},[76,8527,368],{"emptyLinePlaceholder":23},[76,8529,8530],{"class":78,"line":12},[76,8531,8532],{},"post(updateRoute);\n",[41,8534,8535],{},"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!",[295,8537,297],{},{"title":11,"searchDepth":12,"depth":12,"links":8539},[8540,8541,8542],{"id":7939,"depth":12,"text":7940},{"id":8107,"depth":12,"text":8108},{"id":8348,"depth":12,"text":8349},[1791,1792],"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":7921,"description":8550},"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",[314,8553,8554,8555,8556],"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":8560,"title":8561,"author":8562,"body":8563,"category":9043,"date":9044,"description":9045,"extension":16,"link":9046,"meta":9047,"navigation":23,"path":9048,"seo":9049,"slug":8567,"stem":9051,"tags":9052,"teaser":9054,"__hash__":9055},"blog/blog/routing-driving-directions-on-android-part-1-get-the-route.md","Routing / Driving directions on Android – Part 1: Get the route",[26],{"type":8,"value":8564,"toc":9034},[8565,8568,8583,8586,8590,8593,8596,8605,8608,8643,8646,8650,8654,8657,8663,8671,8674,8751,8754,8791,8794,8797,8806,8810,8813,8816,8856,8865,9009,9013,9016,9024,9028,9031],[37,8566,8561],{"id":8567},"routing-driving-directions-on-android-part-1-get-the-route",[41,8569,8570,8571,8576,8577,8582],{},"Complementary to Sebastian’s posts\nabout ",[45,8572,8575],{"href":8573,"rel":8574},"http://mobile.synyx.de/2010/04/google-maps-on-android/",[49],"how to navigate with the MapView","\nand ",[45,8578,8581],{"href":8579,"rel":8580},"http://mobile.synyx.de/2010/05/google-maps-on-android-part-2-overlays/",[49],"how to add customized overlays to it",", I\nwant to show you, how you display the route between two points.",[41,8584,8585],{},"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.",[56,8587,8589],{"id":8588},"getting-started","Getting started",[41,8591,8592],{},"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…”.",[41,8594,8595],{},"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:",[67,8597,8599],{"className":226,"code":8598,"language":228,"meta":11,"style":11},"synyxGeoPoint = new GeoPoint(49002175, 8394160);\n",[73,8600,8601],{"__ignoreMap":11},[76,8602,8603],{"class":78,"line":79},[76,8604,8598],{},[41,8606,8607],{},"And the user location is also quite easy to get:",[67,8609,8611],{"className":226,"code":8610,"language":228,"meta":11,"style":11},"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",[73,8612,8613,8618,8623,8628,8633,8638],{"__ignoreMap":11},[76,8614,8615],{"class":78,"line":79},[76,8616,8617],{},"locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);\n",[76,8619,8620],{"class":78,"line":12},[76,8621,8622],{},"Criteria criteria = new Criteria();\n",[76,8624,8625],{"class":78,"line":90},[76,8626,8627],{},"criteria.setAccuracy(Criteria.ACCURACY_FINE);\n",[76,8629,8630],{"class":78,"line":96},[76,8631,8632],{},"criteria.setAltitudeRequired(false);\n",[76,8634,8635],{"class":78,"line":102},[76,8636,8637],{},"Location lastKnownLocation =\n",[76,8639,8640],{"class":78,"line":108},[76,8641,8642],{},"locationManager.getLastKnownLocation(locationManager.getBestProvider(criteria, true));\n",[41,8644,8645],{},"With this we get the last known, accurate location of the user. So all there is left now, is to get the route.",[56,8647,8649],{"id":8648},"getting-the-geopoints-from-google-maps","Getting the geopoints from google maps",[621,8651,8653],{"id":8652},"kml-with-driving-directions","Kml (with driving directions)",[41,8655,8656],{},"If you need the driving directions, you can build yourself a url like this one to get a kml file with all the\ninformation:",[41,8658,8659],{},[45,8660,8661],{"href":8661,"rel":8662},"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",[49],[41,8664,8665,8666,1166],{},"(For the list of parameters of google maps, see\nhere: ",[45,8667,8670],{"href":8668,"rel":8669},"https://web.archive.org/web/20080901081831/http://mapki.com/wiki/Google_Map_Parameters",[49],"mapki.com",[41,8672,8673],{},"Here’s how we did it:",[67,8675,8677],{"className":226,"code":8676,"language":228,"meta":11,"style":11},"\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",[73,8678,8679,8683,8688,8693,8698,8703,8708,8713,8718,8723,8728,8732,8737,8742,8747],{"__ignoreMap":11},[76,8680,8681],{"class":78,"line":79},[76,8682,368],{"emptyLinePlaceholder":23},[76,8684,8685],{"class":78,"line":12},[76,8686,8687],{},"public String getUrl(GeoPoint src, GeoPoint dest){\n",[76,8689,8690],{"class":78,"line":90},[76,8691,8692],{},"StringBuilder urlString = new StringBuilder();\n",[76,8694,8695],{"class":78,"line":96},[76,8696,8697],{},"urlString.append(\"http://maps.google.com/maps?f=d&hl=en\");\n",[76,8699,8700],{"class":78,"line":102},[76,8701,8702],{},"urlString.append(\"&saddr=\");\n",[76,8704,8705],{"class":78,"line":108},[76,8706,8707],{},"urlString.append(Double.toString((double) src.getLatitudeE6() / 1.0E6));\n",[76,8709,8710],{"class":78,"line":114},[76,8711,8712],{},"urlString.append(\",\");\n",[76,8714,8715],{"class":78,"line":120},[76,8716,8717],{},"urlString.append(Double.toString((double) src.getLongitudeE6() / 1.0E6));\n",[76,8719,8720],{"class":78,"line":125},[76,8721,8722],{},"urlString.append(\"&daddr=\");// to\n",[76,8724,8725],{"class":78,"line":130},[76,8726,8727],{},"urlString.append(Double.toString((double) dest.getLatitudeE6() / 1.0E6));\n",[76,8729,8730],{"class":78,"line":136},[76,8731,8712],{},[76,8733,8734],{"class":78,"line":142},[76,8735,8736],{},"urlString.append(Double.toString((double) dest.getLongitudeE6() / 1.0E6));\n",[76,8738,8739],{"class":78,"line":147},[76,8740,8741],{},"urlString.append(\"&ie=UTF8&0&om=0&output=kml\");\n",[76,8743,8744],{"class":78,"line":152},[76,8745,8746],{},"return urlString;\n",[76,8748,8749],{"class":78,"line":158},[76,8750,287],{},[41,8752,8753],{},"The file you get from this url looks like this:",[67,8755,8757],{"className":1124,"code":8756,"language":1126,"meta":11,"style":11},"\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",[73,8758,8759,8763,8768,8773,8778,8782,8787],{"__ignoreMap":11},[76,8760,8761],{"class":78,"line":79},[76,8762,368],{"emptyLinePlaceholder":23},[76,8764,8765],{"class":78,"line":12},[76,8766,8767],{},"Hohenheimer Str./B27 to Karlstraße/L561\n",[76,8769,8770],{"class":78,"line":90},[76,8771,8772],{},"....\n",[76,8774,8775],{"class":78,"line":96},[76,8776,8777],{},"Head southeast on Hohenheimer Str./B27 toward Bopserwaldstraße Continue to follow B27\n",[76,8779,8780],{"class":78,"line":102},[76,8781,8772],{},[76,8783,8784],{"class":78,"line":108},[76,8785,8786],{},"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",[76,8788,8789],{"class":78,"line":114},[76,8790,8772],{},[41,8792,8793],{},"(the first number of each pair is the longitude, the second the latitude)",[41,8795,8796],{},"To convert them to GeoPoints use:",[67,8798,8800],{"className":226,"code":8799,"language":228,"meta":11,"style":11},"GeoPoint geoPoint = new GeoPoint((int)(Double.parseDouble(latitude[0])*1E6),(int)(Double.parseDouble(longitude[0])*1E6));\n",[73,8801,8802],{"__ignoreMap":11},[76,8803,8804],{"class":78,"line":79},[76,8805,8799],{},[621,8807,8809],{"id":8808},"json-without-driving-directions","JSON (without driving directions)",[41,8811,8812],{},"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.",[41,8814,8815],{},"again an example of what the server returns:",[67,8817,8819],{"className":6109,"code":8818,"language":6111,"meta":11,"style":11},"{tooltipHtml:\" (572x26#160;km / 5 hours 14 mins)\",polylines:[{id:\"route0\",points:\"se}bIgcwt@BSzA_D??Xh@dC|G??hDlIpBzFrAvC`@`BZjCV|@nApBtDvEx@rA| .....\n",[73,8820,8821],{"__ignoreMap":11},[76,8822,8823,8826,8829,8832,8835,8838,8841,8844,8847,8850,8853],{"class":78,"line":79},[76,8824,8825],{"class":1257},"{",[76,8827,8828],{"class":3925},"tooltipHtml",[76,8830,8831],{"class":1257},":",[76,8833,8834],{"class":3962},"\" (572x26#160;km / 5 hours 14 mins)\"",[76,8836,8837],{"class":1257},",",[76,8839,8840],{"class":3925},"polylines",[76,8842,8843],{"class":1257},":[{id:",[76,8845,8846],{"class":3962},"\"route0\"",[76,8848,8849],{"class":1257},",points:",[76,8851,8852],{"class":3962},"\"se}bIgcwt@BSzA_D??Xh@dC|G??hDlIpBzFrAvC`@`BZjCV|@nApBtDvEx@rA| ....",[76,8854,8855],{"class":1261},".\n",[41,8857,8858,8859,8864],{},"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 ",[45,8860,8863],{"href":8861,"rel":8862},"http://facstaff.unca.edu/mcmcclur/googlemaps/encodepolyline/",[49],"http://facstaff.unca.edu/",") :",[67,8866,8868],{"className":226,"code":8867,"language":228,"meta":11,"style":11},"// 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",[73,8869,8870,8875,8880,8885,8890,8895,8900,8905,8910,8915,8920,8925,8930,8935,8940,8945,8950,8955,8960,8965,8969,8973,8977,8981,8985,8990,8995,9000,9005],{"__ignoreMap":11},[76,8871,8872],{"class":78,"line":79},[76,8873,8874],{},"// get only the encoded geopoints\n",[76,8876,8877],{"class":78,"line":12},[76,8878,8879],{},"encoded = encoded.split(\"points:\"\")[1].split(\"\",\")[0];\n",[76,8881,8882],{"class":78,"line":90},[76,8883,8884],{},"// replace two backslashes by one (some error from the transmission)\n",[76,8886,8887],{"class":78,"line":96},[76,8888,8889],{},"encoded = encoded.replace(\"\\\\\", \"\\\");\n",[76,8891,8892],{"class":78,"line":102},[76,8893,8894],{},"//decoding\n",[76,8896,8897],{"class":78,"line":108},[76,8898,8899],{},"List poly = new ArrayList();\n",[76,8901,8902],{"class":78,"line":114},[76,8903,8904],{}," int index = 0, len = encoded.length();\n",[76,8906,8907],{"class":78,"line":120},[76,8908,8909],{}," int lat = 0, lng = 0;\n",[76,8911,8912],{"class":78,"line":125},[76,8913,8914],{}," while (index \u003C len) {\n",[76,8916,8917],{"class":78,"line":130},[76,8918,8919],{}," int b, shift = 0, result = 0;\n",[76,8921,8922],{"class":78,"line":136},[76,8923,8924],{}," do {\n",[76,8926,8927],{"class":78,"line":142},[76,8928,8929],{}," b = encoded.charAt(index++) - 63;\n",[76,8931,8932],{"class":78,"line":147},[76,8933,8934],{}," result |= (b & 0x1f) \u003C\u003C shift;\n",[76,8936,8937],{"class":78,"line":152},[76,8938,8939],{}," shift += 5;\n",[76,8941,8942],{"class":78,"line":158},[76,8943,8944],{}," } while (b >= 0x20);\n",[76,8946,8947],{"class":78,"line":164},[76,8948,8949],{}," int dlat = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));\n",[76,8951,8952],{"class":78,"line":169},[76,8953,8954],{}," lat += dlat;\n",[76,8956,8957],{"class":78,"line":174},[76,8958,8959],{}," shift = 0;\n",[76,8961,8962],{"class":78,"line":180},[76,8963,8964],{}," result = 0;\n",[76,8966,8967],{"class":78,"line":186},[76,8968,8924],{},[76,8970,8971],{"class":78,"line":191},[76,8972,8929],{},[76,8974,8975],{"class":78,"line":196},[76,8976,8934],{},[76,8978,8979],{"class":78,"line":558},[76,8980,8939],{},[76,8982,8983],{"class":78,"line":563},[76,8984,8944],{},[76,8986,8987],{"class":78,"line":568},[76,8988,8989],{}," int dlng = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));\n",[76,8991,8992],{"class":78,"line":573},[76,8993,8994],{}," lng += dlng;\n",[76,8996,8997],{"class":78,"line":579},[76,8998,8999],{}," GeoPoint p = new GeoPoint((int) (((double) lat / 1E5) * 1E6), (int) (((double) lng / 1E5) * 1E6));\n",[76,9001,9002],{"class":78,"line":585},[76,9003,9004],{}," poly.add(p);\n",[76,9006,9007],{"class":78,"line":590},[76,9008,105],{},[56,9010,9012],{"id":9011},"getting-the-geopoints-from-open-streetmap","Getting the geopoints from open streetmap",[41,9014,9015],{},"You can also get the geopoints from open streetmap. It’s quite the same procedure, so i don’t write it all again.",[41,9017,9018,9019],{},"Here you can see for yourself: ",[45,9020,9023],{"href":9021,"rel":9022},"http://wiki.openstreetmap.org/wiki/YOURS#Routing_API",[49],"YOURS Routing_API",[56,9025,9027],{"id":9026},"whats-in-the-next-post","What’s in the next post",[41,9029,9030],{},"That’s it for today, in the next post i will show you how to draw the route on your MapView properly.",[295,9032,9033],{},"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":11,"searchDepth":12,"depth":12,"links":9035},[9036,9037,9041,9042],{"id":8588,"depth":12,"text":8589},{"id":8648,"depth":12,"text":8649,"children":9038},[9039,9040],{"id":8652,"depth":90,"text":8653},{"id":8808,"depth":90,"text":8809},{"id":9011,"depth":12,"text":9012},{"id":9026,"depth":12,"text":9027},[1791,1792],"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":8561,"description":9050},"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",[314,9053,8554,8556],"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":9057,"title":9058,"author":9059,"body":9060,"category":9297,"date":9298,"description":9299,"extension":16,"link":9300,"meta":9301,"navigation":23,"path":9302,"seo":9303,"slug":9064,"stem":9305,"tags":9306,"teaser":9308,"__hash__":9309},"blog/blog/performance-optimization-in-synyx-sudoku.md","Performance optimization in Synyx Sudoku",[26],{"type":8,"value":9061,"toc":9295},[9062,9065,9074,9077,9080,9083,9086,9095,9098,9101,9104,9148,9151,9262,9265,9280,9287,9290,9293],[37,9063,9058],{"id":9064},"performance-optimization-in-synyx-sudoku",[41,9066,9067,9068,9073],{},"Now that ",[45,9069,9072],{"href":9070,"rel":9071},"http://mobile.synyx.de/2010/04/22/release-of-synyxsudoku",[49],"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.",[41,9075,9076],{},"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.",[41,9078,9079],{},"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.",[41,9081,9082],{},"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.",[41,9084,9085],{},"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.",[41,9087,9088,9089,9094],{},"After a little bit of research we found the solution to this in the google I/O\nvideo ",[45,9090,9093],{"href":9091,"rel":9092},"http://www.youtube.com/watch?v=N6YdwzAvwOA",[49],"“Make your Android UI Fast and Efficient”",", which you really should\nwatch, if you haven’t by now!",[41,9096,9097],{},"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.",[41,9099,9100],{},"And that’s how it’s looking in the code:",[41,9102,9103],{},"First you have to overwrite the BaseAdapter from android so that you can give it your specific views to display:",[67,9105,9107],{"className":226,"code":9106,"language":228,"meta":11,"style":11},"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",[73,9108,9109,9114,9119,9124,9129,9134,9139,9144],{"__ignoreMap":11},[76,9110,9111],{"class":78,"line":79},[76,9112,9113],{},"public class HighscoreListAdapter extends BaseAdapter {\n",[76,9115,9116],{"class":78,"line":12},[76,9117,9118],{}," private List valueList;\n",[76,9120,9121],{"class":78,"line":90},[76,9122,9123],{}," private LayoutInflater inflater;\n",[76,9125,9126],{"class":78,"line":96},[76,9127,9128],{}," public HighscoreListAdapter(List highscoreValueList, LayoutInflater inflater) {\n",[76,9130,9131],{"class":78,"line":102},[76,9132,9133],{}," this.inflater = inflater;\n",[76,9135,9136],{"class":78,"line":108},[76,9137,9138],{}," this.valueList = highscoreValueList;\n",[76,9140,9141],{"class":78,"line":114},[76,9142,9143],{}," }\n",[76,9145,9146],{"class":78,"line":120},[76,9147,287],{},[41,9149,9150],{},"and also overwrite the getView method from it, so that you can make your recycling in there:",[67,9152,9154],{"className":226,"code":9153,"language":228,"meta":11,"style":11},"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",[73,9155,9156,9160,9165,9170,9175,9180,9185,9190,9195,9200,9205,9210,9214,9219,9224,9228,9233,9238,9243,9248,9253,9258],{"__ignoreMap":11},[76,9157,9158],{"class":78,"line":79},[76,9159,5766],{},[76,9161,9162],{"class":78,"line":12},[76,9163,9164],{}," HighscoreViewHolder highscoreViewHolder;\n",[76,9166,9167],{"class":78,"line":90},[76,9168,9169],{}," if (convertView == null) {\n",[76,9171,9172],{"class":78,"line":96},[76,9173,9174],{}," // the first few elements of the list are created out of the xml\n",[76,9176,9177],{"class":78,"line":102},[76,9178,9179],{}," convertView = inflater.inflate(R.layout.highscore_list_entry, null);\n",[76,9181,9182],{"class":78,"line":108},[76,9183,9184],{}," highscoreViewHolder = new HighscoreViewHolder();\n",[76,9186,9187],{"class":78,"line":114},[76,9188,9189],{}," highscoreViewHolder.name = (TextView) convertView.findViewById(R.id.highscore_list_entry_name);\n",[76,9191,9192],{"class":78,"line":120},[76,9193,9194],{}," highscoreViewHolder.score = (TextView) convertView.findViewById(R.id.highscore_list_entry_score);\n",[76,9196,9197],{"class":78,"line":125},[76,9198,9199],{}," highscoreViewHolder.rank = (TextView) convertView.findViewById(R.id.highscore_list_entry_rank);\n",[76,9201,9202],{"class":78,"line":130},[76,9203,9204],{}," convertView.setTag(highscoreViewHolder);\n",[76,9206,9207],{"class":78,"line":136},[76,9208,9209],{}," convertView.setFocusable(false);\n",[76,9211,9212],{"class":78,"line":142},[76,9213,3708],{},[76,9215,9216],{"class":78,"line":147},[76,9217,9218],{}," // recycle of the view that went out of the view\n",[76,9220,9221],{"class":78,"line":152},[76,9222,9223],{}," highscoreViewHolder = (HighscoreViewHolder) convertView.getTag();\n",[76,9225,9226],{"class":78,"line":158},[76,9227,9143],{},[76,9229,9230],{"class":78,"line":164},[76,9231,9232],{}," HighscoreValue value = this.valueList.get(position);\n",[76,9234,9235],{"class":78,"line":169},[76,9236,9237],{}," // set the new values\n",[76,9239,9240],{"class":78,"line":174},[76,9241,9242],{}," highscoreViewHolder.name.setText(value.getName());\n",[76,9244,9245],{"class":78,"line":180},[76,9246,9247],{}," highscoreViewHolder.score.setText(Integer.toString(value.getScore()));\n",[76,9249,9250],{"class":78,"line":186},[76,9251,9252],{}," highscoreViewHolder.rank.setText(Integer.toString(value.getRank()));\n",[76,9254,9255],{"class":78,"line":191},[76,9256,9257],{}," return convertView;\n",[76,9259,9260],{"class":78,"line":196},[76,9261,287],{},[41,9263,9264],{},"now whats only left is to create the adapter, and assign it to the ListView:",[67,9266,9268],{"className":226,"code":9267,"language":228,"meta":11,"style":11},"highscoreListAdapter = new HighscoreListAdapter(highscoreList, getLayoutInflater());\nlistView.setAdapter(highscoreListAdapter);\n",[73,9269,9270,9275],{"__ignoreMap":11},[76,9271,9272],{"class":78,"line":79},[76,9273,9274],{},"highscoreListAdapter = new HighscoreListAdapter(highscoreList, getLayoutInflater());\n",[76,9276,9277],{"class":78,"line":12},[76,9278,9279],{},"listView.setAdapter(highscoreListAdapter);\n",[41,9281,9282,9283,9286],{},"If you make some changes in the data set you gave to the adapter, just call ",[1159,9284,9285],{},"notifyDataSetChanged()"," on it to let it\nrefresh itself.",[41,9288,9289],{},"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.",[41,9291,9292],{},"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.",[295,9294,297],{},{"title":11,"searchDepth":12,"depth":12,"links":9296},[],[1791,1792],"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":9058,"description":9304},"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",[314,9307],"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":9311,"title":9312,"author":9313,"body":9314,"category":9434,"date":9435,"description":9436,"extension":16,"link":9437,"meta":9438,"navigation":23,"path":9439,"seo":9440,"slug":9318,"stem":9442,"tags":9443,"teaser":9445,"__hash__":9446},"blog/blog/user-statistics-from-synyxsudoku.md","User statistics from SynyxSudoku",[26],{"type":8,"value":9315,"toc":9432},[9316,9319,9327,9335,9338,9382,9385,9429],[37,9317,9312],{"id":9318},"user-statistics-from-synyxsudoku",[41,9320,9321,9322,9326],{},"First of all, I was quite surprised as i saw that 70% of the ",[45,9323,9072],{"href":9324,"rel":9325},"http://tinyurl.com/SynyxSudoku",[49]," 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%.",[41,9328,9329,9330,9334],{},"So as you can imagine, we’ve got quite a few samples of data since\nthe ",[45,9331,1804],{"href":9332,"rel":9333},"http://mobile.synyx.de/2010/04/22/release-of-synyxsudoku/",[49],", on that we now want to give you a little\noverview.",[41,9336,9337],{},"The devices came mostly with the latest Android versions available:",[9339,9340,9341,9354],"table",{},[9342,9343,9344],"thead",{},[9345,9346,9347,9351],"tr",{},[9348,9349,9350],"th",{},"Version",[9348,9352,9353],{},"Count",[9355,9356,9357,9366,9374],"tbody",{},[9345,9358,9359,9363],{},[9360,9361,9362],"td",{},"1.6",[9360,9364,9365],{},"39%",[9345,9367,9368,9371],{},[9360,9369,9370],{},"2.1",[9360,9372,9373],{},"11%",[9345,9375,9376,9379],{},[9360,9377,9378],{},"2.1-update1",[9360,9380,9381],{},"50%",[41,9383,9384],{},"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.",[9339,9386,9387,9396],{},[9342,9388,9389],{},[9345,9390,9391,9394],{},[9348,9392,9393],{},"Resolution",[9348,9395,9353],{},[9355,9397,9398,9406,9414,9422],{},[9345,9399,9400,9403],{},[9360,9401,9402],{},"240×320",[9360,9404,9405],{},"9%",[9345,9407,9408,9411],{},[9360,9409,9410],{},"320×480",[9360,9412,9413],{},"33%",[9345,9415,9416,9419],{},[9360,9417,9418],{},"480×800",[9360,9420,9421],{},"25%",[9345,9423,9424,9427],{},[9360,9425,9426],{},"480×854",[9360,9428,9413],{},[41,9430,9431],{},"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":11,"searchDepth":12,"depth":12,"links":9433},[],[1791,6393],"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":9312,"description":9441},"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",[314,9444],"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":9448,"title":9449,"author":9450,"body":9451,"category":9489,"date":9490,"description":9491,"extension":16,"link":9492,"meta":9493,"navigation":23,"path":9494,"seo":9495,"slug":9497,"stem":9498,"tags":9499,"teaser":9501,"__hash__":9502},"blog/blog/synyxsudoku-update-to-version-1-02.md","SynyxSudoku update to version 1.02",[26],{"type":8,"value":9452,"toc":9487},[9453,9456,9463,9466,9477,9480,9483],[37,9454,9449],{"id":9455},"synyxsudoku-update-to-version-102",[41,9457,9458,9459,9462],{},"There were a few little things that had to be changed on SynyxSudoku after\nthe ",[45,9460,1804],{"href":9332,"rel":9461},[49],", so today we uploaded an update to the version\n1.02.",[41,9464,9465],{},"Here’s a quick changelog:",[341,9467,9468,9471,9474],{},[344,9469,9470],{},"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.)",[344,9472,9473],{},"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).",[344,9475,9476],{},"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…)",[41,9478,9479],{},"If you notice any bugs or if something bothers you about the game, please leave a comment, or write us an email.",[41,9481,9482],{},"Download:",[41,9484,9485],{},[213,9486],{"alt":11,"src":9324},{"title":11,"searchDepth":12,"depth":12,"links":9488},[],[1791,6393],"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":9449,"description":9496},"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",[314,9500],"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":9504,"title":9505,"author":9506,"body":9507,"category":9551,"date":9552,"description":9553,"extension":16,"link":9554,"meta":9555,"navigation":23,"path":9556,"seo":9557,"slug":9511,"stem":9558,"tags":9559,"teaser":9561,"__hash__":9562},"blog/blog/release-of-synyxsudoku.md","Release of SynyxSudoku",[26],{"type":8,"value":9508,"toc":9549},[9509,9512,9515,9518,9521,9524,9527,9530,9534,9537],[37,9510,9505],{"id":9511},"release-of-synyxsudoku",[41,9513,9514],{},"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 🙂 )",[41,9516,9517],{},"SynyxSudoku offers 3 difficulty levels, containing nearly unlimited puzzling fun due to new created sudokus each time\nyou launch!",[41,9519,9520],{},"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.",[41,9522,9523],{},"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?”.",[41,9525,9526],{},"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!",[41,9528,9529],{},"Download our game by searching for Synyx Sudoku on the android market or using this qr-code:",[41,9531,9532],{},[213,9533],{"alt":11,"src":9324},[41,9535,9536],{},"If you are curious how SynyxSudoku looks like, here we have a few screenshots:",[41,9538,9539,9542,9543,9542,9546],{},[213,9540],{"alt":11,"src":9541},"https://media.synyx.de/uploads//2010/04/sudoku_1.png"," ",[213,9544],{"alt":11,"src":9545},"https://media.synyx.de/uploads//2010/04/sudoku_menu_en.png",[213,9547],{"alt":11,"src":9548},"https://media.synyx.de/uploads//2010/04/sudoku_4_en.png",{"title":11,"searchDepth":12,"depth":12,"links":9550},[],[1791,6393],"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":9505,"description":9514},"blog/release-of-synyxsudoku",[314,9560,9500],"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":9564,"title":9565,"author":9566,"body":9567,"category":9585,"date":9586,"description":9574,"extension":16,"link":9587,"meta":9588,"navigation":23,"path":9589,"seo":9590,"slug":9591,"stem":9592,"tags":9593,"teaser":9594,"__hash__":9595},"blog/blog/sudoku-on-android-2.md","Sudoku on Android",[26],{"type":8,"value":9568,"toc":9583},[9569,9572,9575,9580],[37,9570,9565],{"id":9571},"sudoku-on-android",[41,9573,9574],{},"Here’s a quick preview of our upcoming sudoku app on android:",[41,9576,9577],{},[76,9578,9579],{},"youtube S_wjWf7V660",[41,9581,9582],{},"The layout has yet to be polished a little, but altogether the app is nearly finished.",{"title":11,"searchDepth":12,"depth":12,"links":9584},[],[1791,6393],"2010-04-15T18:49:51","https://synyx.de/blog/sudoku-on-android-2/",{},"/blog/sudoku-on-android-2",{"title":9565,"description":9574},"sudoku-on-android-2","blog/sudoku-on-android-2",[314,9560],"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",["Reactive",9597],{"$scookieConsent":9598,"$ssite-config":9600},{"functional":9599,"analytics":9599},false,{"_priority":9601,"env":9605,"name":9606,"url":9607},{"name":9602,"env":9603,"url":9604},-10,-15,0,"production","nuxt-app","https://synyx.de",["Set"],["ShallowReactive",9610],{"author-knell":-1,"roughlyFilteredArticles":-1},"/blog/author/knell"]